Address Contract Verified
Address
0x39D167Fe676EFC3be49bE874a37349A5D89f9058
Balance
0 ETH
Nonce
1
Code Size
23368 bytes
Creator
0xcfaD496f...a781 at tx 0x24658f69...60f677
Indexed Transactions
0
Contract Bytecode
23368 bytes
0x6080604052600436106101f8575f3560e01c80638d01f0ba11610117578063c9f7e596116100ac578063e4cb99c01161007c578063f0c9e46511610062578063f0c9e46514610674578063f18524c014610688578063f9adbf111461069b575f80fd5b8063e4cb99c014610636578063efdf0bb014610655575f80fd5b8063c9f7e5961461059f578063d336c82d146105b2578063dfc0791c146105d9578063e0bfa258146105f8575f80fd5b8063b56bcd35116100e7578063b56bcd35146104fa578063b86cb7cb14610519578063b8b71de114610538578063b9181d1c1461056c575f80fd5b80638d01f0ba146103f05780638e5fa59c146104235780638e8f294b14610442578063a92d64e0146104db575f80fd5b8063459be9861161018d5780637c65d3fc1161015d5780637c65d3fc14610378578063822c08891461038b5780638237e538146103aa57806382ab11b3146103dd575f80fd5b8063459be98614610306578063465c2447146103255780634879539f1461033a57806376daa13e14610359575f80fd5b8063218751b2116101c8578063218751b21461028657806323e30c8b146102a557806325240810146102d25780632ab9535a146102f1575f80fd5b80630715940e146102035780630d7fc7bc1461022457806312d43a51146102375780631d0ef6c014610273575f80fd5b366101ff57005b5f80fd5b34801561020e575f80fd5b5061022261021d366004614d4c565b6106ba565b005b610222610232366004614d75565b61077c565b348015610242575f80fd5b50600154610256906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b610222610281366004614d4c565b610ad0565b348015610291575f80fd5b50600354610256906001600160a01b031681565b3480156102b0575f80fd5b506102c46102bf366004614e12565b610d08565b60405190815260200161026a565b3480156102dd575f80fd5b50600254610256906001600160a01b031681565b3480156102fc575f80fd5b506102c460045481565b348015610311575f80fd5b50610222610320366004614e89565b610eef565b348015610330575f80fd5b506102c460055481565b348015610345575f80fd5b50610222610354366004614ec1565b611400565b348015610364575f80fd5b50610222610373366004614d4c565b6115c0565b610222610386366004614f5e565b611685565b348015610396575f80fd5b506102226103a5366004614d75565b61181c565b3480156103b5575f80fd5b506102c47f439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd981565b6102226103eb36600461501d565b611aca565b3480156103fb575f80fd5b506102c47f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf181565b34801561042e575f80fd5b5061022261043d366004614ec1565b611cf8565b34801561044d575f80fd5b506104a661045c366004614d4c565b60076020525f90815260409020805460018201546002909201546001600160a01b039182169282169181169074010000000000000000000000000000000000000000900460ff1684565b60405161026a94939291906001600160a01b039485168152928416602084015292166040820152901515606082015260800190565b3480156104e6575f80fd5b506102226104f5366004615089565b611e79565b348015610505575f80fd5b50610222610514366004614ec1565b6121eb565b348015610524575f80fd5b506102226105333660046150c0565b612205565b348015610543575f80fd5b50610557610552366004615123565b612384565b6040805192835260208301919091520161026a565b348015610577575f80fd5b506102c47f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a81565b6102226105ad366004615164565b612605565b3480156105bd575f80fd5b50610256736c5fdc0c53b122ae0f15a863c349f3a481de8f1f81565b3480156105e4575f80fd5b506102226105f3366004615246565b612701565b348015610603575f80fd5b50610626610612366004614d4c565b60066020525f908152604090205460ff1681565b604051901515815260200161026a565b348015610641575f80fd5b5061022261065036600461501d565b612cb0565b348015610660575f80fd5b5061022261066f366004614d4c565b612db2565b34801561067f575f80fd5b50610222612e57565b61022261069636600461529f565b612f06565b3480156106a6575f80fd5b506102226106b5366004615368565b613065565b6001546001600160a01b031633146106fe576040517fb577c1f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03811661073e576040517ffc9dfba700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03165f90815260066020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316866001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af11580156107d7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107fb919061539a565b6001600160a01b031614610870576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f4e6f7420616e20455448206d61726b657400000000000000000000000000000060448201526064015b60405180910390fd5b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004015f604051808303818588803b1580156108bd575f80fd5b505af11580156108cf573d5f803e3d5ffd5b505060405163095ea7b360e01b81526001600160a01b038a16600482015234602482015273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2935063095ea7b3925060440190505f604051808303815f87803b15801561092d575f80fd5b505af115801561093f573d5f803e3d5ffd5b50506040517f47e7ef240000000000000000000000000000000000000000000000000000000081523360048201523460248201526001600160a01b03891692506347e7ef2491506044015f604051808303815f87803b1580156109a0575f80fd5b505af11580156109b2573d5f803e3d5ffd5b50506040517f1ef08b75000000000000000000000000000000000000000000000000000000008152336004820152602481018890526044810187905260ff861660648201526084810185905260a481018490526001600160a01b0389169250631ef08b75915060c4015f604051808303815f87803b158015610a32575f80fd5b505af1158015610a44573d5f803e3d5ffd5b50506040517fa9059cbb0000000000000000000000000000000000000000000000000000000081523360048201526024810188905273865377367054516e17014ccded1e7d814edc9ce4925063a9059cbb91506044015b5f604051808303815f87803b158015610ab2575f80fd5b505af1158015610ac4573d5f803e3d5ffd5b50505050505050505050565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316816001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015610b2b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b4f919061539a565b6001600160a01b031614610bbf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f4e6f7420616e20455448206d61726b65740000000000000000000000000000006044820152606401610867565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004015f604051808303818588803b158015610c0c575f80fd5b505af1158015610c1e573d5f803e3d5ffd5b505060405163095ea7b360e01b81526001600160a01b038516600482015234602482015273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2935063095ea7b3925060440190505f604051808303815f87803b158015610c7c575f80fd5b505af1158015610c8e573d5f803e3d5ffd5b50506040517f47e7ef240000000000000000000000000000000000000000000000000000000081523360048201523460248201526001600160a01b03841692506347e7ef2491506044015f604051808303815f87803b158015610cef575f80fd5b505af1158015610d01573d5f803e3d5ffd5b5050505050565b5f6001600160a01b0387163014610d56576040517f09aeae910000000000000000000000000000000000000000000000000000000081526001600160a01b0388166004820152602401610867565b33736c5fdc0c53b122ae0f15a863c349f3a481de8f1f14610da5576040517f3ca39932000000000000000000000000000000000000000000000000000000008152336004820152602401610867565b5f610db283850185615599565b505050505050505090507f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf18103610e2757610e228685858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506134fe92505050565b610ec2565b7f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a8103610e8d57610e228685858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250613a1192505050565b6040517fa2160dcf00000000000000000000000000000000000000000000000000000000815260048101829052602401610867565b507f439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd9979650505050505050565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523360048201525f9073ad038eb671c44b853887a7e32528fab35dc5d710906370a0823190602401602060405180830381865afa158015610f57573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f7b9190615654565b90508082111561101a576040517f23b872dd0000000000000000000000000000000000000000000000000000000081523360048201523060248201526044810182905273ad038eb671c44b853887a7e32528fab35dc5d710906323b872dd906064015f604051808303815f87803b158015610ff4575f80fd5b505af1158015611006573d5f803e3d5ffd5b5050505061101581843061402d565b6110aa565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081523360048201523060248201526044810183905273ad038eb671c44b853887a7e32528fab35dc5d710906323b872dd906064015f604051808303815f87803b158015611089575f80fd5b505af115801561109b573d5f803e3d5ffd5b505050506110aa82843061402d565b6040517f2ecd4e7d0000000000000000000000000000000000000000000000000000000081523360048201525f906001600160a01b03871690632ecd4e7d906024016020604051808303815f875af1158015611108573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061112c9190615654565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529091505f9073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa158015611197573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111bb9190615654565b90505f8683106111cb57866111cd565b825b90508082101561127c5773865377367054516e17014ccded1e7d814edc9ce46323b872dd33306111fd8686615698565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b1681526001600160a01b03938416600482015292909116602483015260448201526064015f604051808303815f87803b158015611261575f80fd5b505af1158015611273573d5f803e3d5ffd5b50505050611313565b73865377367054516e17014ccded1e7d814edc9ce463a9059cbb336112a18486615698565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044015f604051808303815f87803b1580156112fc575f80fd5b505af115801561130e573d5f803e3d5ffd5b505050505b60405163095ea7b360e01b81526001600160a01b03891660048201526024810182905273865377367054516e17014ccded1e7d814edc9ce49063095ea7b3906044015f604051808303815f87803b15801561136c575f80fd5b505af115801561137e573d5f803e3d5ffd5b50506040517f22867d78000000000000000000000000000000000000000000000000000000008152336004820152602481018490526001600160a01b038b1692506322867d7891506044015f604051808303815f87803b1580156113e0575f80fd5b505af11580156113f2573d5f803e3d5ffd5b505050505050505050505050565b5f896001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af115801561143e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611462919061539a565b6040517f23b872dd000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481018b90529091506001600160a01b038216906323b872dd906064015f604051808303815f87803b1580156114c9575f80fd5b505af11580156114db573d5f803e3d5ffd5b505060405163095ea7b360e01b81526001600160a01b038d81166004830152602482018d90528416925063095ea7b391506044015f604051808303815f87803b158015611526575f80fd5b505af1158015611538573d5f803e3d5ffd5b50506040517f47e7ef24000000000000000000000000000000000000000000000000000000008152336004820152602481018c90526001600160a01b038d1692506347e7ef2491506044015f604051808303815f87803b15801561159a575f80fd5b505af11580156115ac573d5f803e3d5ffd5b50505050610ac48a89898989898989612cb0565b6001546001600160a01b03163314611604576040517fb577c1f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038116611644576040517ffc9dfba700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03165f90815260066020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b61168d6140e2565b6001600160a01b038881165f90815260076020526040902054166116e8576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b0389166004820152602401610867565b5f7f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf1338a8a5f8b8b8b8b8b8b60405160200161172e9b9a99989796959493929190615721565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290527f5cffe9de0000000000000000000000000000000000000000000000000000000082529150736c5fdc0c53b122ae0f15a863c349f3a481de8f1f90635cffe9de906117c690309073865377367054516e17014ccded1e7d814edc9ce4908f90879060040161582a565b6020604051808303815f875af11580156117e2573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611806919061585b565b505061181160015f55565b505050505050505050565b6040517f3525f591000000000000000000000000000000000000000000000000000000008152336004820152602481018690526044810185905260ff841660648201526084810183905260a481018290526001600160a01b03871690633525f5919060c4015f604051808303815f87803b158015611898575f80fd5b505af11580156118aa573d5f803e3d5ffd5b505050505f866001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af11580156118ec573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611910919061539a565b90506001600160a01b03811673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214611998576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f4e6f7420616e20455448206d61726b65740000000000000000000000000000006044820152606401610867565b6040517f2e1a7d4d0000000000000000000000000000000000000000000000000000000081526004810187905273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d906024015f604051808303815f87803b1580156119fb575f80fd5b505af1158015611a0d573d5f803e3d5ffd5b50506040515f925033915088908381818185875af1925050503d805f8114611a50576040519150601f19603f3d011682016040523d82523d5f602084013e611a55565b606091505b5050905080611ac0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4661696c656420746f207472616e7366657220455448000000000000000000006044820152606401610867565b5050505050505050565b5f886001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611b08573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b2c919061539a565b90506001600160a01b03811673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214611bb4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f4d61726b6574206973206e6f7420616e20455448206d61726b657400000000006044820152606401610867565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004015f604051808303818588803b158015611c01575f80fd5b505af1158015611c13573d5f803e3d5ffd5b505060405163095ea7b360e01b81526001600160a01b038d811660048301523460248301528516935063095ea7b3925060440190505f604051808303815f87803b158015611c5f575f80fd5b505af1158015611c71573d5f803e3d5ffd5b50506040517f47e7ef240000000000000000000000000000000000000000000000000000000081523360048201523460248201526001600160a01b038c1692506347e7ef2491506044015f604051808303815f87803b158015611cd2575f80fd5b505af1158015611ce4573d5f803e3d5ffd5b505050506118118989898989898989612cb0565b611d0489898989610eef565b6040517f3525f591000000000000000000000000000000000000000000000000000000008152336004820152602481018690526044810185905260ff841660648201526084810183905260a481018290526001600160a01b038a1690633525f5919060c4015f604051808303815f87803b158015611d80575f80fd5b505af1158015611d92573d5f803e3d5ffd5b50505050886001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611dd3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611df7919061539a565b6040517fa9059cbb000000000000000000000000000000000000000000000000000000008152336004820152602481018790526001600160a01b03919091169063a9059cbb906044015b5f604051808303815f87803b158015611e58575f80fd5b505af1158015611e6a573d5f803e3d5ffd5b50505050505050505050505050565b6001546001600160a01b03163314611ebd576040517fb577c1f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038281165f9081526007602052604090205416611f18576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610867565b6001600160a01b038116611f58576040517ff79ccfb200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038281165f9081526007602052604080822060028101549054915163095ea7b360e01b81529084166004820181905260248201939093529192169063095ea7b3906044016020604051808303815f875af1158015611fbf573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611fe3919061585b565b506001600160a01b038381165f9081526007602052604080822060010154905163095ea7b360e01b8152848416600482015260248101929092529091169063095ea7b3906044016020604051808303815f875af1158015612046573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061206a919061585b565b506001600160a01b038381165f90815260076020526040908190206002810180547fffffffffffffffffffffffff0000000000000000000000000000000000000000168685169081179091559054915163095ea7b360e01b815260048101919091525f19602482015291169063095ea7b3906044016020604051808303815f875af11580156120fb573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061211f919061585b565b506001600160a01b038381165f908152600760205260409081902060010154905163095ea7b360e01b815284831660048201525f19602482015291169063095ea7b3906044016020604051808303815f875af1158015612181573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121a5919061585b565b50816001600160a01b0316836001600160a01b03167f06bc331032840de3e44179cee67a8860508a9c70737f1e433476e3a9a255b25f60405160405180910390a3505050565b6121f789898989610eef565b61181189868686868661181c565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081523360048201523060248201526044810187905273865377367054516e17014ccded1e7d814edc9ce4906323b872dd906064015f604051808303815f87803b158015612274575f80fd5b505af1158015612286573d5f803e3d5ffd5b505060405163095ea7b360e01b81526001600160a01b038a1660048201526024810189905273865377367054516e17014ccded1e7d814edc9ce4925063095ea7b391506044015f604051808303815f87803b1580156122e3575f80fd5b505af11580156122f5573d5f803e3d5ffd5b50506040517f22867d78000000000000000000000000000000000000000000000000000000008152336004820152602481018990526001600160a01b038a1692506322867d7891506044015f604051808303815f87803b158015612357575f80fd5b505af1158015612369573d5f803e3d5ffd5b5050505061237b87868686868661181c565b50505050505050565b5f808481612393600283615876565b600354600554600480546040517f556d6e9f000000000000000000000000000000000000000000000000000000008152918201929092526024810191909152604481018590529192505f916001600160a01b039091169063556d6e9f90606401602060405180830381865afa15801561240e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906124329190615654565b90505f6301e13380886124458b876158ae565b61244f91906158c1565b6124599190615876565b90505f8183116124725761246d8383615698565b61247c565b61247c8284615698565b90505f5b888110156125ce5785838511156124a25761249b8682615698565b90506124af565b6124ac86826158ae565b90505b600354600554600480546040517f556d6e9f000000000000000000000000000000000000000000000000000000008152918201929092526024810191909152604481018390525f916001600160a01b03169063556d6e9f90606401602060405180830381865afa158015612525573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125499190615654565b90505f6301e133808d8f8561255e91906158ae565b61256891906158c1565b6125729190615876565b90505f81831161258b576125868383615698565b612595565b6125958284615698565b9050858110156125ac578297508196508095508399505b6125b760028a615876565b985050505050806125c7906158d8565b9050612480565b50846301e133808a6125e0838e6158ae565b6125ea91906158c1565b6125f49190615876565b965096505050505050935093915050565b8a5f0361263e576040517f9a4f66bf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f811561266857506001600160a01b03808a165f9081526007602052604090206001015416612684565b506001600160a01b03808a165f90815260076020526040902054165b6126996001600160a01b03821633308f614123565b806001600160a01b0316336001600160a01b03168b6001600160a01b03167f7cfff908a4b583f36430b25d75964c458d8ede8a99bd61be750e97ee1b2f3a968f6040516126e891815260200190565b60405180910390a46113f28b8b8b8b8b8b8b8b8b611685565b6001546001600160a01b03163314612745576040517fb577c1f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015273ad038eb671c44b853887a7e32528fab35dc5d71090638e8f294b90602401602060405180830381865afa1580156127b4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127d8919061585b565b612819576040517f2df59b680000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610867565b5f846001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015612857573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061287b919061539a565b90506001600160a01b0383161580156128a65750806001600160a01b0316846001600160a01b031614155b15612963578484866001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af11580156128ea573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061290e919061539a565b6040517f119a599e0000000000000000000000000000000000000000000000000000000081526001600160a01b0393841660048201529183166024830152821660448201529084166064820152608401610867565b6001600160a01b038581165f818152600760205260409081902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000908116898616908117835560019092018054909116948616949094179093555163095ea7b360e01b815260048101919091525f19602482015263095ea7b3906044016020604051808303815f875af11580156129fe573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612a22919061585b565b50806001600160a01b0316846001600160a01b031614612ac4576001600160a01b038581165f818152600760205260409081902060010154905163095ea7b360e01b815260048101929092525f1960248301529091169063095ea7b3906044016020604051808303815f875af1158015612a9e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ac2919061585b565b505b6001600160a01b03831615612c0f576001600160a01b038581165f90815260076020526040908190206002810180547fffffffffffffffffffffffff0000000000000000000000000000000000000000168785169081179091559054915163095ea7b360e01b815260048101919091525f19602482015291169063095ea7b3906044016020604051808303815f875af1158015612b63573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612b87919061585b565b506001600160a01b038581165f908152600760205260409081902060010154905163095ea7b360e01b815285831660048201525f19602482015291169063095ea7b3906044016020604051808303815f875af1158015612be9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c0d919061585b565b505b6001600160a01b038581165f8181526007602090815260409182902060020180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000881515021790559051848416815286841693881692917f8ab0b8c470b101bb508ea695012478540f30a9857917e6c327eda5761d6f8be7910160405180910390a45050505050565b5f612cbb87896158ae565b6040517f1ef08b75000000000000000000000000000000000000000000000000000000008152336004820152602481018290526044810187905260ff861660648201526084810185905260a481018490529091506001600160a01b038a1690631ef08b759060c4015f604051808303815f87803b158015612d3a575f80fd5b505af1158015612d4c573d5f803e3d5ffd5b50505050612d5b87873361419f565b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000081523360048201526024810189905273865377367054516e17014ccded1e7d814edc9ce49063a9059cbb90604401611e41565b6001546001600160a01b03163314612df6576040517fb577c1f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600280547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0383169081179091556040517ff74ae56780e3765c0c0897ef57fb50a10a237584f419631812daf040913e1c9f905f90a250565b6002546001600160a01b03163314612e9b576040517f7c04d72b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028054600180546001600160a01b0383167fffffffffffffffffffffffff000000000000000000000000000000000000000091821681179092559091169091556040517f639717155292ce2c3e699929a8b65d14a637640f75ab5b6d165a4e735d82a455905f90a2565b612f0e6140e2565b6001600160a01b038981165f9081526007602052604090205416612f69576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b038a166004820152602401610867565b5f7f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a338b8b8b8b8b8b8b8b8b604051602001612faf9b9a999897969594939291906158f0565b6040516020818303038152906040529050736c5fdc0c53b122ae0f15a863c349f3a481de8f1f6001600160a01b0316635cffe9de3073865377367054516e17014ccded1e7d814edc9ce48e856040518563ffffffff1660e01b815260040161301a949392919061582a565b6020604051808303815f875af1158015613036573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061305a919061585b565b5050610ac460015f55565b6001546001600160a01b031633146130a9576040517fb577c1f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fc661065700000000000000000000000000000000000000000000000000000000815260048101839052839073865377367054516e17014ccded1e7d814edc9ce4906001600160a01b0383169063c661065790602401602060405180830381865afa15801561311d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613141919061539a565b6001600160a01b0316146131b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f57726f6e6720646f6c6120696e646578000000000000000000000000000000006044820152606401610867565b6040517fc66106570000000000000000000000000000000000000000000000000000000081526004810183905273ad038eb671c44b853887a7e32528fab35dc5d710906001600160a01b0383169063c661065790602401602060405180830381865afa158015613223573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613247919061539a565b6001600160a01b0316146132b7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f57726f6e672064627220696e64657800000000000000000000000000000000006044820152606401610867565b60035460405163095ea7b360e01b81526001600160a01b0390911660048201525f602482015273865377367054516e17014ccded1e7d814edc9ce49063095ea7b3906044015f604051808303815f87803b158015613313575f80fd5b505af1158015613325573d5f803e3d5ffd5b505060035460405163095ea7b360e01b81526001600160a01b0390911660048201525f602482015273ad038eb671c44b853887a7e32528fab35dc5d710925063095ea7b391506044015f604051808303815f87803b158015613385575f80fd5b505af1158015613397573d5f803e3d5ffd5b5050600380547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b038581169190911790915560405163095ea7b360e01b815290871660048201525f19602482015273865377367054516e17014ccded1e7d814edc9ce4925063095ea7b391506044015f604051808303815f87803b158015613425575f80fd5b505af1158015613437573d5f803e3d5ffd5b505060405163095ea7b360e01b81526001600160a01b03871660048201525f19602482015273ad038eb671c44b853887a7e32528fab35dc5d710925063095ea7b391506044015f604051808303815f87803b158015613494575f80fd5b505af11580156134a6573d5f803e3d5ffd5b505050600584905550600482905560408051848152602081018490526001600160a01b038616917f419bc165e35cf07f7814bf17c3caf51ac411a3161153b0e1f3b1cdb11ac81876910160405180910390a250505050565b5f805f805f805f878060200190518101906135199190615a0b565b6001600160a01b0387165f90815260076020526040902060020154979f50959d50939b5090995097509095509093505074010000000000000000000000000000000000000000900460ff161590506136c1576001600160a01b0385165f9081526006602052604090205460ff166135bc576040517ffc9dfba700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405163095ea7b360e01b81526001600160a01b0386166004820152602481018a905273865377367054516e17014ccded1e7d814edc9ce49063095ea7b3906044015f604051808303815f87803b158015613615575f80fd5b505af1158015613627573d5f803e3d5ffd5b505050505f856001600160a01b031634866040516136459190615ac7565b5f6040518083038185875af1925050503d805f811461367f576040519150601f19603f3d011682016040523d82523d5f602084013e613684565b606091505b50509050806136bf576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b6001600160a01b038681165f908152600760205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919216906370a0823190602401602060405180830381865afa15801561372e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906137529190615654565b9050805f0361378d576040517fb4f18b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038781165f9081526007602052604090206002015416156137be576137bb88828986614214565b90505b6001600160a01b038781165f81815260076020526040908190206001015490517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015291926347e7ef24928c92909116906370a0823190602401602060405180830381865afa158015613839573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061385d9190615654565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044015f604051808303815f87803b1580156138b8575f80fd5b505af11580156138ca573d5f803e3d5ffd5b505050506138db888b86858b61439d565b6040820151156139745760408281015190517fa9059cbb0000000000000000000000000000000000000000000000000000000081526001600160a01b038a166004820152602481019190915273865377367054516e17014ccded1e7d814edc9ce49063a9059cbb906044015f604051808303815f87803b15801561395d575f80fd5b505af115801561396f573d5f803e3d5ffd5b505050505b81511580159061398757505f8260200151115b1561399e5761399e825f015183602001518a61419f565b6139a8888b6145c2565b604080830151835182518d8152602081018590529283019190915260608201526001600160a01b03808a1691908916907f27adca66c335786d061cb91c67c649d181361d7245b6d38c4995b1f9b94017729060800160405180910390a350505050505050505050565b5f805f805f805f8088806020019051810190613a2d9190615a0b565b9850985098509850985098509850985050613a4c888b8786858c61476b565b6001600160a01b038088165f9081526007602052604090208054600290910154908216911615613b4d57613a8389878a848761497c565b6001600160a01b038981165f908152600760205260408082206001015490517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015293995090929116906370a0823190602401602060405180830381865afa158015613af7573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613b1b9190615654565b90508015613b4b576001600160a01b03808a165f90815260076020526040902060010154613b4b91168b83614af2565b505b6001600160a01b0388165f9081526007602052604090206002015474010000000000000000000000000000000000000000900460ff1615613d50576001600160a01b0387165f9081526006602052604090205460ff16613bd9576040517ffc9dfba700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405163095ea7b360e01b81526001600160a01b0388811660048301525f602483015282169063095ea7b3906044016020604051808303815f875af1158015613c24573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613c48919061585b565b5060405163095ea7b360e01b81526001600160a01b0388811660048301526024820188905282169063095ea7b3906044016020604051808303815f875af1158015613c95573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613cb9919061585b565b505f876001600160a01b03163487604051613cd49190615ac7565b5f6040518083038185875af1925050503d805f8114613d0e576040519150601f19603f3d011682016040523d82523d5f602084013e613d13565b606091505b5050905080613d4e576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b6001600160a01b038881165f9081526007602052604090206002015416613e3b576001600160a01b038881165f908152600760205260408082206001015490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919216906370a0823190602401602060405180830381865afa158015613de1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613e059190615654565b90508015613e35576001600160a01b03808a165f90815260076020526040902060010154613e3591168b83614af2565b50613efe565b6001600160a01b03811673865377367054516e17014ccded1e7d814edc9ce414613efe576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015613ebc573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613ee09190615654565b90508015613efc57613efc6001600160a01b0383168b83614af2565b505b815115801590613f1157505f8260200151115b15613fb95781516040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038b166004820152306024820152604481019190915273ad038eb671c44b853887a7e32528fab35dc5d710906323b872dd906064015f604051808303815f87803b158015613f91575f80fd5b505af1158015613fa3573d5f803e3d5ffd5b50505050613fb9825f015183602001518b61402d565b613fc3898c6145c2565b604080830151835182518e8152602081018a90529283019190915260608201526001600160a01b03808b1691908a16907fc9557b65f45f298d465e24590087886d84c9edd6e108069afb1a834ba704b7669060800160405180910390a35050505050505050505050565b82156140dd57600354600480546005546040517fa64833a000000000000000000000000000000000000000000000000000000000815292830191909152602482015260448101859052606481018490526001600160a01b0383811660848301529091169063a64833a09060a4015b6020604051808303815f875af11580156140b7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906140db9190615654565b505b505050565b60025f540361411d576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60025f55565b6040516001600160a01b0384811660248301528381166044830152606482018390526140db9186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050614b23565b82156140dd57600354600554600480546040517fa64833a000000000000000000000000000000000000000000000000000000000815291820192909252602481019190915260448101859052606481018490526001600160a01b0383811660848301529091169063a64833a09060a40161409b565b6001600160a01b038083165f908152600760205260408082206002015490517fc87ae3340000000000000000000000000000000000000000000000000000000081529192839291169063c87ae3349061427590899089908890600401615ae2565b6020604051808303815f875af1158015614291573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906142b59190615654565b6001600160a01b038581165f908152600760205260408082206001015490517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015293945090929116906370a0823190602401602060405180830381865afa158015614329573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061434d9190615654565b905081811015614393576040517f278c9d400000000000000000000000000000000000000000000000000000000081526004810183905260248101829052604401610867565b9695505050505050565b815160408301515f91906143b190876158ae565b6143bb91906158ae565b84516020860151604080880151606089015191517f1ef08b750000000000000000000000000000000000000000000000000000000081526001600160a01b038c8116600483015260248201879052604482019590955260ff9093166064840152608483015260a4820152919250831690631ef08b759060c4015f604051808303815f87803b15801561444b575f80fd5b505af115801561445d573d5f803e3d5ffd5b50506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015283925073865377367054516e17014ccded1e7d814edc9ce491506370a0823190602401602060405180830381865afa1580156144c9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906144ed9190615654565b10156145ba576040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152819073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa15801561455b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061457f9190615654565b6040517fc1c3f18500000000000000000000000000000000000000000000000000000000815260048101929092526024820152604401610867565b505050505050565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa15801561462a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061464e9190615654565b905081811015614694576040517fd694ba950000000000000000000000000000000000000000000000000000000081526004810183905260248101829052604401610867565b818111156147335773865377367054516e17014ccded1e7d814edc9ce463a9059cbb846146c18585615698565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044015f604051808303815f87803b15801561471c575f80fd5b505af115801561472e573d5f803e3d5ffd5b505050505b47156140dd576040516001600160a01b038416904780156108fc02915f818181858888f193505050501580156140db573d5f803e3d5ffd5b60408201511561481b5760408281015190517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b0388166004820152306024820152604481019190915273865377367054516e17014ccded1e7d814edc9ce4906323b872dd906064015f604051808303815f87803b1580156147f3575f80fd5b505af1158015614805573d5f803e3d5ffd5b50505060408301516148189150866158ae565b94505b60405163095ea7b360e01b81526001600160a01b03821660048201526024810186905273865377367054516e17014ccded1e7d814edc9ce49063095ea7b3906044015f604051808303815f87803b158015614874575f80fd5b505af1158015614886573d5f803e3d5ffd5b50506040517f22867d780000000000000000000000000000000000000000000000000000000081526001600160a01b03898116600483015260248201899052841692506322867d7891506044015f604051808303815f87803b1580156148ea575f80fd5b505af11580156148fc573d5f803e3d5ffd5b505084516020860151604080880151606089015191517f3525f5910000000000000000000000000000000000000000000000000000000081526001600160a01b038d81166004830152602482018c9052604482019590955260ff9093166064840152608483015260a48201529084169250633525f591915060c401610a9b565b6001600160a01b038084165f908152600760205260408082206002015490517f33525192000000000000000000000000000000000000000000000000000000008152919283929116906333525192906149dd908a908a908890600401615ae2565b6020604051808303815f875af11580156149f9573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190614a1d9190615654565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa158015614a7d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190614aa19190615654565b905081811015614ae7576040517fe50972c50000000000000000000000000000000000000000000000000000000081526004810183905260248101829052604401610867565b979650505050505050565b6040516001600160a01b038381166024830152604482018390526140dd91859182169063a9059cbb90606401614158565b5f614b376001600160a01b03841683614b9d565b905080515f14158015614b5b575080806020019051810190614b59919061585b565b155b156140dd576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610867565b6060614baa83835f614bb3565b90505b92915050565b606081471015614bf8576040517fcf47918100000000000000000000000000000000000000000000000000000000815247600482015260248101839052604401610867565b5f80856001600160a01b03168486604051614c139190615ac7565b5f6040518083038185875af1925050503d805f8114614c4d576040519150601f19603f3d011682016040523d82523d5f602084013e614c52565b606091505b5091509150614c62868383614c6e565b925050505b9392505050565b606082614c8357614c7e82614ce3565b614c67565b8151158015614c9a57506001600160a01b0384163b155b15614cdc576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610867565b5080614c67565b805115614cf35780518082602001fd5b6040517fd6bda27500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b6001600160a01b0381168114614d25575f80fd5b8035614d4781614d28565b919050565b5f60208284031215614d5c575f80fd5b8135614c6781614d28565b60ff81168114614d25575f80fd5b5f805f805f8060c08789031215614d8a575f80fd5b8635614d9581614d28565b955060208701359450604087013593506060870135614db381614d67565b9598949750929560808101359460a0909101359350915050565b5f8083601f840112614ddd575f80fd5b50813567ffffffffffffffff811115614df4575f80fd5b602083019150836020828501011115614e0b575f80fd5b9250929050565b5f805f805f8060a08789031215614e27575f80fd5b8635614e3281614d28565b95506020870135614e4281614d28565b94506040870135935060608701359250608087013567ffffffffffffffff811115614e6b575f80fd5b614e7789828a01614dcd565b979a9699509497509295939492505050565b5f805f8060808587031215614e9c575f80fd5b8435614ea781614d28565b966020860135965060408601359560600135945092505050565b5f805f805f805f805f6101208a8c031215614eda575f80fd5b8935614ee581614d28565b985060208a0135975060408a0135965060608a0135955060808a0135945060a08a0135935060c08a0135614f1881614d67565b8093505060e08a013591506101008a013590509295985092959850929598565b5f60808284031215614f48575f80fd5b50919050565b5f60608284031215614f48575f80fd5b5f805f805f805f805f6101808a8c031215614f77575f80fd5b8935985060208a0135614f8981614d28565b975060408a0135614f9981614d28565b965060608a013567ffffffffffffffff80821115614fb5575f80fd5b614fc18d838e01614dcd565b9098509650869150614fd68d60808e01614f38565b95506101008c0135915080821115614fec575f80fd5b50614ff98c828d01614dcd565b909450925061500e90508b6101208c01614f4e565b90509295985092959850929598565b5f805f805f805f80610100898b031215615035575f80fd5b883561504081614d28565b97506020890135965060408901359550606089013594506080890135935060a089013561506c81614d67565b979a969950949793969295929450505060c08201359160e0013590565b5f806040838503121561509a575f80fd5b82356150a581614d28565b915060208301356150b581614d28565b809150509250929050565b5f805f805f805f60e0888a0312156150d6575f80fd5b87356150e181614d28565b9650602088013595506040880135945060608801359350608088013561510681614d67565b9699959850939692959460a0840135945060c09093013592915050565b5f805f60608486031215615135575f80fd5b505081359360208301359350604090920135919050565b8015158114614d25575f80fd5b8035614d478161514c565b5f805f805f805f805f805f6101c08c8e03121561517f575f80fd5b8b359a5060208c0135995061519760408d0135614d28565b60408c013598506151ab60608d0135614d28565b60608c0135975067ffffffffffffffff8060808e013511156151cb575f80fd5b6151db8e60808f01358f01614dcd565b90985096506151ed8e60a08f01614f38565b9550806101208e01351115615200575f80fd5b506152128d6101208e01358e01614dcd565b90945092506152258d6101408e01614f4e565b91506152346101a08d01615159565b90509295989b509295989b9093969950565b5f805f8060808587031215615259575f80fd5b843561526481614d28565b9350602085013561527481614d28565b9250604085013561528481614d28565b915060608501356152948161514c565b939692955090935050565b5f805f805f805f805f806101a08b8d0312156152b9575f80fd5b8a35995060208b01356152cb81614d28565b985060408b01356152db81614d28565b975060608b0135965060808b013567ffffffffffffffff808211156152fe575f80fd5b61530a8e838f01614dcd565b909850965086915061531f8e60a08f01614f38565b95506101208d0135915080821115615335575f80fd5b506153428d828e01614dcd565b909450925061535790508c6101408d01614f4e565b90509295989b9194979a5092959850565b5f805f6060848603121561537a575f80fd5b833561538581614d28565b95602085013595506040909401359392505050565b5f602082840312156153aa575f80fd5b8151614c6781614d28565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6040516080810167ffffffffffffffff81118282101715615405576154056153b5565b60405290565b6040516060810167ffffffffffffffff81118282101715615405576154056153b5565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715615475576154756153b5565b604052919050565b5f67ffffffffffffffff821115615496576154966153b5565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b5f82601f8301126154d1575f80fd5b81356154e46154df8261547d565b61542e565b8181528460208386010111156154f8575f80fd5b816020850160208301375f918101602001919091529392505050565b5f60808284031215615524575f80fd5b61552c6153e2565b905081358152602082013561554081614d67565b80602083015250604082013560408201526060820135606082015292915050565b5f60608284031215615571575f80fd5b61557961540b565b905081358152602082013560208201526040820135604082015292915050565b5f805f805f805f805f6101c08a8c0312156155b2575f80fd5b8935985060208a01356155c481614d28565b975060408a01356155d481614d28565b96506155e260608b01614d3c565b955060808a0135945060a08a013567ffffffffffffffff80821115615605575f80fd5b6156118d838e016154c2565b95506156208d60c08e01615514565b94506101408c0135915080821115615636575f80fd5b506156438c828d016154c2565b92505061500e8b6101608c01615561565b5f60208284031215615664575f80fd5b5051919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b81810381811115614bad57614bad61566b565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b80358252602081013561570481614d67565b60ff16602083015260408181013590830152606090810135910152565b5f6101c08d83526001600160a01b03808e166020850152808d166040850152808c1660608501525060ff8a1660808401528060a0840152615765818401898b6156ab565b905061577460c08401886156f2565b8281036101408401526157888186886156ab565b8435610160850152602085013561018085015260408501356101a085015291506157af9050565b9c9b505050505050505050505050565b5f5b838110156157d95781810151838201526020016157c1565b50505f910152565b5f81518084526157f88160208601602086016157bf565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b5f6001600160a01b0380871683528086166020840152508360408301526080606083015261439360808301846157e1565b5f6020828403121561586b575f80fd5b8151614c678161514c565b5f826158a9577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b500490565b80820180821115614bad57614bad61566b565b8082028115828204841417614bad57614bad61566b565b5f5f1982036158e9576158e961566b565b5060010190565b5f6101c08d83526001600160a01b03808e166020850152808d166040850152808c166060850152508960808401528060a0840152615765818401898b6156ab565b8051614d4781614d28565b5f82601f83011261594b575f80fd5b81516159596154df8261547d565b81815284602083860101111561596d575f80fd5b61597e8260208301602087016157bf565b949350505050565b5f60808284031215615996575f80fd5b61599e6153e2565b90508151815260208201516159b281614d67565b80602083015250604082015160408201526060820151606082015292915050565b5f606082840312156159e3575f80fd5b6159eb61540b565b905081518152602082015160208201526040820151604082015292915050565b5f805f805f805f805f6101c08a8c031215615a24575f80fd5b8951985060208a0151615a3681614d28565b60408b0151909850615a4781614d28565b9650615a5560608b01615931565b955060808a0151945060a08a015167ffffffffffffffff80821115615a78575f80fd5b615a848d838e0161593c565b9550615a938d60c08e01615986565b94506101408c0151915080821115615aa9575f80fd5b50615ab68c828d0161593c565b92505061500e8b6101608c016159d3565b5f8251615ad88184602087016157bf565b9190910192915050565b6001600160a01b0384168152826020820152606060408201525f615b0960608301846157e1565b9594505050505056fea2646970667358221220137063af3d45bb0845e36e5a5eabfbd7945f69c7cfd1846904684c7b5bf9895f64736f6c63430008140033
Verified Source Code Full Match
Compiler: v0.8.20+commit.a1b79de6
EVM: shanghai
Optimization: Yes (10000 runs)
ALEV2.sol 716 lines
//SPDX-License-Identifier: None
pragma solidity ^0.8.0;
import "src/interfaces/IMarket.sol";
import "src/interfaces/IPendleHelper.sol";
import {CurveHelper} from "src/util/CurveHelper.sol";
import {ReentrancyGuard} from "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
interface IDBR {
function markets(address) external view returns (bool);
}
interface IERC3156FlashBorrower {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}
interface IERC3156FlashLender {
/**
* @dev Initiate a flash loan.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param token The loan currency.
* @param value The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 value,
bytes calldata data
) external returns (bool);
}
// Accelerated leverage engine
contract ALEV2 is
ReentrancyGuard,
CurveHelper,
IERC3156FlashBorrower
{
using SafeERC20 for IERC20;
error CollateralNotSet();
error MarketNotSet(address market);
error SwapFailed();
error DOLAInvalidBorrow(uint256 expected, uint256 actual);
error DOLAInvalidRepay(uint256 expected, uint256 actual);
error InvalidProxyAddress();
error InvalidHelperAddress();
error InvalidAction(bytes32 action);
error NotFlashMinter(address caller);
error NotALE(address caller);
error NothingToDeposit();
error DepositFailed(uint256 expected, uint256 actual);
error WithdrawFailed(uint256 expected, uint256 actual);
error TotalSupplyChanged(uint256 expected, uint256 actual);
error CollateralIsZero();
error NoMarket(address market);
error MarketSetupFailed(
address market,
address buySellToken,
address collateral,
address helper
);
mapping(address => bool) public isExchangeProxy;
IERC3156FlashLender public constant flash =
IERC3156FlashLender(0x6C5Fdc0c53b122Ae0f15a863C349f3A481DE8f1F);
bytes32 public constant CALLBACK_SUCCESS =
keccak256("ERC3156FlashBorrower.onFlashLoan");
bytes32 public constant LEVERAGE = keccak256("LEVERAGE");
bytes32 public constant DELEVERAGE = keccak256("DELEVERAGE");
struct Market {
IERC20 buySellToken;
IERC20 collateral;
IPendleHelper helper;
bool useProxy;
}
struct Permit {
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
struct DBRHelper {
uint256 amountIn; // DOLA or DBR
uint256 minOut; // DOLA or DBR
uint256 dola; // DOLA to extra borrow or extra repay
}
event LeverageUp(
address indexed market,
address indexed account,
uint256 dolaFlashMinted, // DOLA flash minted for buying collateral only
uint256 collateralDeposited, // amount of collateral deposited into the escrow
uint256 dolaBorrowed, // amount of DOLA borrowed on behalf of the user
uint256 dolaForDBR // amount of DOLA used for buying DBR
);
event LeverageDown(
address indexed market,
address indexed account,
uint256 dolaFlashMinted, // Flash minted DOLA for repaying leverage only
uint256 collateralSold, // amount of collateral/underlying sold
uint256 dolaUserRepaid, // amount of DOLA deposited by the user as part of the repay
uint256 dbrSoldForDola // amount of DBR sold for DOLA
);
event Deposit(
address indexed market,
address indexed account,
address indexed token, // token used for initial deposit (could be collateral or buySellToken)
uint256 depositAmount
);
event NewMarket(
address indexed market,
address indexed buySellToken,
address collateral,
address indexed helper
);
event NewHelper(address indexed market, address indexed helper);
// Mapping of market to Market structs
// NOTE: in normal cases sellToken/buyToken is the collateral token,
// in other cases it could be different (eg. st-yCRV is collateral, yCRV is the token to be swapped from/to DOLA)
// or with DOLA curve LPs, LP token is the collateral and DOLA is the token to be swapped from/to
mapping(address => Market) public markets;
constructor(
address _pool,
address _gov
) CurveHelper(_pool, _gov) {
DOLA.approve(address(flash), type(uint).max);
}
/// @notice Allow an exchange proxy
/// @param _proxy The proxy address
function allowProxy(address _proxy) external onlyGov {
if (_proxy == address(0)) revert InvalidProxyAddress();
isExchangeProxy[_proxy] = true;
}
/// @notice Deny an exchange proxy
/// @param _proxy The proxy address
function denyProxy(address _proxy) external onlyGov {
if (_proxy == address(0)) revert InvalidProxyAddress();
isExchangeProxy[_proxy] = false;
}
/// @notice Set the market for a collateral token
/// @param _buySellToken The token which will be bought/sold (usually the collateral token), probably underlying if there's a helper
/// @param _market The market contract
/// @param _helper Optional helper contract to transform collateral to buySelltoken and viceversa
/// @param useProxy Whether to use the Exchange Proxy or not
function setMarket(
address _market,
address _buySellToken,
address _helper,
bool useProxy
) external onlyGov {
if (!IDBR(address(DBR)).markets(_market)) revert NoMarket(_market);
address collateral = IMarket(_market).collateral();
if (_helper == address(0) && _buySellToken != collateral) {
revert MarketSetupFailed(
_market,
_buySellToken,
IMarket(_market).collateral(),
_helper
);
}
markets[_market].buySellToken = IERC20(_buySellToken);
markets[_market].collateral = IERC20(collateral);
markets[_market].buySellToken.approve(_market, type(uint256).max);
if ( _buySellToken != collateral) {
markets[_market].collateral.approve(_market, type(uint256).max);
}
if (_helper != address(0)) {
markets[_market].helper = IPendleHelper(_helper);
markets[_market].buySellToken.approve(_helper, type(uint256).max);
markets[_market].collateral.approve(_helper, type(uint256).max);
}
markets[_market].useProxy = useProxy;
emit NewMarket(_market, _buySellToken, collateral, _helper);
}
/// @notice Update the helper contract
/// @param _market The market we want to update the helper contract for
/// @param _helper The helper contract
function updateMarketHelper(
address _market,
address _helper
) external onlyGov {
if (address(markets[_market].buySellToken) == address(0))
revert MarketNotSet(_market);
if (_helper == address(0)) revert InvalidHelperAddress();
address oldHelper = address(markets[_market].helper);
markets[_market].buySellToken.approve(oldHelper, 0);
markets[_market].collateral.approve(oldHelper, 0);
markets[_market].helper = IPendleHelper(_helper);
markets[_market].buySellToken.approve(_helper, type(uint256).max);
markets[_market].collateral.approve(_helper, type(uint256).max);
emit NewHelper(_market, _helper);
}
/// @notice Leverage user position by minting DOLA, buying collateral, deposting into the user escrow and borrow DOLA on behalf to repay the minted DOLA
/// @dev Requires user to sign message to permit the contract to borrow DOLA on behalf
/// @param value Amount of DOLA to flash mint/burn
/// @param market The market contract
/// @param exchangeProxy The exchange proxy contract if any
/// @param swapCallData The `data` field from the API response.
/// @param permit Permit data
/// @param helperData Optional helper data in case the collateral needs to be transformed
/// @param dbrData Optional data in case the user wants to buy DBR and also withdraw some DOLA
function leveragePosition(
uint256 value,
address market,
address exchangeProxy,
bytes calldata swapCallData,
Permit calldata permit,
bytes calldata helperData,
DBRHelper calldata dbrData
) public payable nonReentrant {
if (address(markets[market].buySellToken) == address(0))
revert MarketNotSet(market);
bytes memory data = abi.encode(
LEVERAGE,
msg.sender,
market,
exchangeProxy,
0, // unused
swapCallData,
permit,
helperData,
dbrData
);
flash.flashLoan(
IERC3156FlashBorrower(address(this)),
address(DOLA),
value,
data
);
}
/// @notice Deposit collateral and instantly leverage user position by minting DOLA, buying collateral, deposting into the user escrow and borrow DOLA on behalf to repay the minted DOLA
/// @dev Requires user to sign message to permit the contract to borrow DOLA on behalf
/// @param initialDeposit Amount of collateral or underlying (in case of helper) to deposit
/// @param value Amount of DOLA to borrow
/// @param market The market address
/// @param exchangeProxy The exchange proxy contract if any
/// @param swapCallData The `data` field from the API response.
/// @param permit Permit data
/// @param helperData Optional helper data in case the collateral needs to be transformed
/// @param dbrData Optional data in case the user wants to buy DBR and also withdraw some DOLA
/// @param depositCollateral Whether the initialDeposit is the collateral or the underlying entry asset
function depositAndLeveragePosition(
uint256 initialDeposit,
uint256 value,
address market,
address exchangeProxy,
bytes calldata swapCallData,
Permit calldata permit,
bytes calldata helperData,
DBRHelper calldata dbrData,
bool depositCollateral
) external payable {
if (initialDeposit == 0) revert NothingToDeposit();
IERC20 depositToken;
if (depositCollateral) {
depositToken = markets[market].collateral;
} else {
depositToken = markets[market].buySellToken;
}
depositToken.safeTransferFrom(
msg.sender,
address(this),
initialDeposit
);
emit Deposit(market, msg.sender, address(depositToken), initialDeposit);
leveragePosition(
value,
market,
exchangeProxy,
swapCallData,
permit,
helperData,
dbrData
);
}
/// @notice Repay a DOLA loan and withdraw collateral from the escrow
/// @dev Requires user to sign message to permit the contract to withdraw collateral from the escrow
/// @param value Amount of DOLA to repay
/// @param market The market contract
/// @param exchangeProxy The exchange proxy contract if any
/// @param collateralAmount Collateral amount to withdraw from the escrow
/// @param swapCallData The `data` field from the API response.
/// @param permit Permit data
/// @param helperData Optional helper data in case collateral needs to be transformed
/// @param dbrData Optional data in case the user wants to sell DBR
function deleveragePosition(
uint256 value,
address market,
address exchangeProxy,
uint256 collateralAmount,
bytes calldata swapCallData,
Permit calldata permit,
bytes calldata helperData,
DBRHelper calldata dbrData
) external payable nonReentrant {
if (address(markets[market].buySellToken) == address(0))
revert MarketNotSet(market);
bytes memory data = abi.encode(
DELEVERAGE,
msg.sender,
market,
exchangeProxy,
collateralAmount,
swapCallData,
permit,
helperData,
dbrData
);
flash.flashLoan(
IERC3156FlashBorrower(address(this)),
address(DOLA),
value,
data
);
}
function onFlashLoan(
address initiator,
address,
uint256 amount,
uint256,
bytes calldata data
) external returns (bytes32) {
if (initiator != address(this)) revert NotALE(initiator);
if (msg.sender != address(flash)) revert NotFlashMinter(msg.sender);
(bytes32 ACTION, , , , , , , , ) = abi.decode(
data,
(
bytes32,
address,
address,
address,
uint256,
bytes,
Permit,
bytes,
DBRHelper
)
);
if (ACTION == LEVERAGE) _onFlashLoanLeverage(amount, data);
else if (ACTION == DELEVERAGE) _onFlashLoanDeleverage(amount, data);
else revert InvalidAction(bytes32(ACTION));
return CALLBACK_SUCCESS;
}
function _onFlashLoanLeverage(uint256 _value, bytes memory data) internal {
(
,
address _user,
address _market,
address _proxy,
,
bytes memory _swapCallData,
Permit memory _permit,
bytes memory _helperData,
DBRHelper memory _dbrData
) = abi.decode(
data,
(
bytes32,
address,
address,
address,
uint256,
bytes,
Permit,
bytes,
DBRHelper
)
);
// Call the encoded swap function call on the contract at `swapTarget`,
// passing along any ETH attached to this function call to cover protocol fees.
if (markets[_market].useProxy) {
if(!isExchangeProxy[_proxy]) revert InvalidProxyAddress();
DOLA.approve(_proxy, _value);
(bool success, ) = payable(_proxy).call{value: msg.value}(
_swapCallData
);
if (!success) revert SwapFailed();
}
// Actual collateral/buyToken bought
uint256 collateralAmount = markets[_market].buySellToken.balanceOf(
address(this)
);
if (collateralAmount == 0) revert CollateralIsZero();
// If there's a helper contract, the buyToken has to be transformed
if (address(markets[_market].helper) != address(0)) {
collateralAmount = _convertToCollateral(
_user,
collateralAmount,
_market,
_helperData
);
}
// Deposit and borrow on behalf
IMarket(_market).deposit(
_user,
markets[_market].collateral.balanceOf(address(this))
);
_borrowDola(_user, _value, _permit, _dbrData, IMarket(_market));
if (_dbrData.dola != 0) DOLA.transfer(_user, _dbrData.dola);
if (_dbrData.amountIn > 0 && _dbrData.minOut > 0)
_buyDbr(_dbrData.amountIn, _dbrData.minOut, _user);
_refundExcess(_user, _value);
emit LeverageUp(
_market,
_user,
_value,
collateralAmount,
_dbrData.dola,
_dbrData.amountIn
);
}
function _onFlashLoanDeleverage(
uint256 _value,
bytes memory data
) internal {
(
,
address _user,
address _market,
address _proxy,
uint256 _collateralAmount,
bytes memory _swapCallData,
Permit memory _permit,
bytes memory _helperData,
DBRHelper memory _dbrData
) = abi.decode(
data,
(
bytes32,
address,
address,
address,
uint256,
bytes,
Permit,
bytes,
DBRHelper
)
);
_repayAndWithdraw(
_user,
_value,
_collateralAmount,
_permit,
_dbrData,
IMarket(_market)
);
IERC20 sellToken = markets[_market].buySellToken;
// If there's a helper contract, the collateral has to be transformed
if (address(markets[_market].helper) != address(0)) {
_collateralAmount = _convertToAsset(
_user,
_collateralAmount,
_market,
sellToken,
_helperData
);
// Reimburse leftover collateral from conversion if any
uint256 collateralLeft = markets[_market].collateral.balanceOf(
address(this)
);
if (collateralLeft != 0) {
markets[_market].collateral.safeTransfer(_user, collateralLeft);
}
}
// Call the encoded swap function call on the contract at `swapTarget`,
// passing along any ETH attached to this function call to cover protocol fees.
// NOTE: This will swap the collateral or helperCollateral for DOLA
if (markets[_market].useProxy) {
if(!isExchangeProxy[_proxy]) revert InvalidProxyAddress();
// Approve sellToken for exchangeProxy
sellToken.approve(_proxy, 0);
sellToken.approve(_proxy, _collateralAmount);
(bool success, ) = payable(_proxy).call{value: msg.value}(
_swapCallData
);
if (!success) revert SwapFailed();
}
if (address(markets[_market].helper) == address(0)) {
uint256 collateralAvailable = markets[_market].collateral.balanceOf(
address(this)
);
if (collateralAvailable != 0) {
markets[_market].collateral.safeTransfer(
_user,
collateralAvailable
);
}
} else if (address(sellToken) != address(DOLA)) {
uint256 sellTokenBal = sellToken.balanceOf(address(this));
// Send any leftover sellToken to the sender
if (sellTokenBal != 0) sellToken.safeTransfer(_user, sellTokenBal);
}
if (_dbrData.amountIn > 0 && _dbrData.minOut > 0) {
DBR.transferFrom(_user, address(this), _dbrData.amountIn);
_sellDbr(_dbrData.amountIn, _dbrData.minOut, _user);
}
_refundExcess(_user, _value);
emit LeverageDown(
_market,
_user,
_value,
_collateralAmount,
_dbrData.dola,
_dbrData.amountIn
);
}
/// @notice Borrow DOLA on behalf of the user
/// @param _value Amount of DOLA to borrow
/// @param _permit Permit data
/// @param _dbrData DBR data
/// @param market The market contract
function _borrowDola(
address _user,
uint256 _value,
Permit memory _permit,
DBRHelper memory _dbrData,
IMarket market
) internal {
uint256 dolaToBorrow = _value + _dbrData.dola + _dbrData.amountIn;
// We borrow the amount of DOLA we minted before plus the amount for buying DBR if any
market.borrowOnBehalf(
_user,
dolaToBorrow,
_permit.deadline,
_permit.v,
_permit.r,
_permit.s
);
if (DOLA.balanceOf(address(this)) < dolaToBorrow)
revert DOLAInvalidBorrow(
dolaToBorrow,
DOLA.balanceOf(address(this))
);
}
/// @notice Repay DOLA loan and withdraw collateral from the escrow
/// @param _value Amount of DOLA to repay
/// @param _collateralAmount Collateral amount to withdraw from the escrow
/// @param _permit Permit data
/// @param _dbrData DBR data
/// @param market The market contract
function _repayAndWithdraw(
address _user,
uint256 _value,
uint256 _collateralAmount,
Permit memory _permit,
DBRHelper memory _dbrData,
IMarket market
) internal {
if (_dbrData.dola != 0) {
DOLA.transferFrom(_user, address(this), _dbrData.dola);
_value += _dbrData.dola;
}
DOLA.approve(address(market), _value);
market.repay(_user, _value);
// withdraw amount from ZERO EX quote
market.withdrawOnBehalf(
_user,
_collateralAmount,
_permit.deadline,
_permit.v,
_permit.r,
_permit.s
);
}
/// @notice convert a collateral amount into the underlying asset
/// @param _user The user address
/// @param _collateralAmount Collateral amount to convert
/// @param _market The market contract
/// @param sellToken The sell token (the underlying asset)
/// @param _helperData Optional helper data
/// @return assetAmount The amount of sellToken/underlying after the conversion
function _convertToAsset(
address _user,
uint256 _collateralAmount,
address _market,
IERC20 sellToken,
bytes memory _helperData
) internal returns (uint256) {
// Collateral amount is now converted into sellToken
uint256 assetAmount = markets[_market].helper.convertFromCollateral(
_user,
_collateralAmount,
_helperData
);
uint256 actualAssetAmount = sellToken.balanceOf(address(this));
if (actualAssetAmount < assetAmount)
revert WithdrawFailed(assetAmount, actualAssetAmount);
return actualAssetAmount;
}
/// @notice convert the underlying asset amount into the collateral
/// @param _user The user address
/// @param _assetAmount The amount of sellToken/underlying to convert
/// @param _market The market contract
/// @param _helperData Optional helper data
/// @return collateralAmount The amount of collateral after the conversion
function _convertToCollateral(
address _user,
uint256 _assetAmount,
address _market,
bytes memory _helperData
) internal returns (uint256) {
// Collateral amount is now converted
uint256 collateralAmount = markets[_market].helper.convertToCollateral(
_user,
_assetAmount,
_helperData
);
uint256 actualCollateralAmount = markets[_market].collateral.balanceOf(
address(this)
);
if (actualCollateralAmount < collateralAmount)
revert DepositFailed(collateralAmount, actualCollateralAmount);
return actualCollateralAmount;
}
/// @notice Send any extra DOLA and ETH to the user
/// @param _user The user address
/// @param _value The amount of flash borrowed DOLA to be repaid
function _refundExcess(address _user, uint256 _value) internal {
uint256 balance = DOLA.balanceOf(address(this));
if (balance < _value) revert DOLAInvalidRepay(_value, balance);
// Send any extra DOLA to the sender
if (balance > _value) DOLA.transfer(_user, balance - _value);
// Refund any unspent protocol fees to the sender.
if (address(this).balance > 0)
payable(_user).transfer(address(this).balance);
}
}
IMarket.sol 107 lines
pragma solidity ^0.8.13;
import {IBorrowController, IEscrow, IOracle} from "src/Market.sol";
interface IMarket {
function borrow(uint borrowAmount) external;
function borrowOnBehalf(
address msgSender,
uint dolaAmount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function withdraw(uint amount) external;
function withdrawMax() external;
function withdrawOnBehalf(
address msgSender,
uint amount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function deposit(uint amount) external;
function deposit(address msgSender, uint collateralAmount) external;
function depositAndBorrow(
uint collateralAmount,
uint borrowAmount
) external;
function repay(address msgSender, uint amount) external;
function liquidate(address borrower, uint liquidationAmount) external;
function forceReplenish(address borrower, uint deficitBefore) external;
function collateral() external returns (address);
function debts(address user) external returns (uint);
function recall(uint amount) external;
function invalidateNonce() external;
function pauseBorrows(bool paused) external;
function setBorrowController(IBorrowController borrowController) external;
function escrows(address user) external view returns (IEscrow);
function predictEscrow(address user) external view returns (IEscrow);
function getCollateralValue(address user) external view returns (uint);
function getWithdrawalLimit(address user) external view returns (uint);
function getCreditLimit(address user) external view returns (uint);
function lender() external view returns (address);
function borrowController() external view returns (address);
function escrowImplementation() external view returns (address);
function totalDebt() external view returns (uint);
function borrowPaused() external view returns (bool);
function replenishmentIncentiveBps() external view returns (uint);
function liquidationIncentiveBps() external view returns (uint);
function collateralFactorBps() external view returns (uint);
function setCollateralFactorBps(uint cfBps) external;
function setOracle(IOracle oracle) external;
function setGov(address newGov) external;
function setLender(address newLender) external;
function setPauseGuardian(address newPauseGuardian) external;
function setReplenismentIncentiveBps(uint riBps) external;
function setLiquidationIncentiveBps(uint liBps) external;
function setLiquidationFactorBps(uint lfBps) external;
function setLiquidationFeeBps(uint lfeeBps) external;
function liquidationFeeBps() external view returns (uint);
function DOMAIN_SEPARATOR() external view returns (uint);
function oracle() external view returns (address);
}
IPendleHelper.sol 47 lines
//SPDX-License-Identifier: None
pragma solidity ^0.8.0;
interface IPendleHelper {
struct Permit {
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
function convertToCollateral(
address user,
uint256 amount,
bytes calldata data
) external returns (uint256 collateralAmount);
function convertToCollateral(
uint256 amount,
bytes calldata data
) external returns (uint256 collateralAmount);
function convertToCollateralAndDeposit(
uint256 amount,
address recipient,
bytes calldata data
) external returns (uint256 collateralAmount);
function convertFromCollateral(
address user,
uint256 amount,
bytes calldata data
) external returns (uint256);
function convertFromCollateral(
uint256 amount,
address recipient,
bytes calldata data
) external returns (uint256);
function withdrawAndConvertFromCollateral(
uint256 amount,
address recipient,
Permit calldata permit,
bytes calldata data
) external returns (uint256 underlyingAmount);
}
CurveHelper.sol 112 lines
pragma solidity ^0.8.13;
import "src/util/OffchainAbstractHelper.sol";
import {Ownable} from "src/util/Ownable.sol";
interface ICurvePool {
function coins(uint index) external view returns(address);
function get_dy(uint i, uint j, uint dx) external view returns(uint);
function exchange(uint i, uint j, uint dx, uint min_dy, address receiver) external returns(uint);
function exchange(uint i, uint j, uint dx, uint min_dy) external returns(uint);
}
contract CurveHelper is Ownable, OffchainAbstractHelper {
ICurvePool public curvePool;
uint public dbrIndex = type(uint).max;
uint public dolaIndex = type(uint).max;
event NewCurvePool(address indexed newPool, uint256 dolaIndex, uint256 dbrIndex);
constructor(address _pool, address _gov) Ownable(_gov) {
curvePool = ICurvePool(_pool);
for(uint i; i < 3; ++i){
if(curvePool.coins(i) == address(DOLA)){
dolaIndex = i;
}
else if(curvePool.coins(i) == address(DBR)){
dbrIndex = i;
}
}
require(dolaIndex != type(uint).max && dbrIndex != type(uint).max, "CurveHelper: pool missing DOLA or DBR");
DOLA.approve(_pool, type(uint).max);
DBR.approve(_pool, type(uint).max);
}
/**
@notice Sells an exact amount of DBR for DOLA in a curve pool
@param amount Amount of DBR to sell
@param minOut minimum amount of DOLA to receive
*/
function _sellDbr(uint amount, uint minOut, address receiver) internal override {
if(amount > 0){
curvePool.exchange(dbrIndex, dolaIndex, amount, minOut, receiver);
}
}
/**
@notice Buys an exact amount of DBR for DOLA in a curve pool
@param amount Amount of DOLA to sell
@param minOut minimum amount of DBR out
*/
function _buyDbr(uint amount, uint minOut, address receiver) internal override {
if(amount > 0) {
curvePool.exchange(dolaIndex, dbrIndex, amount, minOut, receiver);
}
}
/**
@notice Approximates the total amount of dola and dbr needed to borrow a dolaBorrowAmount while also borrowing enough to buy the DBR needed to cover for the borrowing period
@dev Uses a binary search to approximate the amounts needed. Should only be called as part of generating transaction parameters.
@param dolaBorrowAmount Amount of dola the user wishes to end up with
@param period Amount of time in seconds the loan will last
@param iterations Number of approximation iterations. The higher the more precise the result
*/
function approximateDolaAndDbrNeeded(uint dolaBorrowAmount, uint period, uint iterations) public view override returns(uint dolaForDbr, uint dbrNeeded){
uint amountIn = dolaBorrowAmount;
uint stepSize = amountIn / 2;
uint dbrReceived = curvePool.get_dy(dolaIndex, dbrIndex, amountIn);
uint dbrToBuy = (amountIn + dolaBorrowAmount) * period / 365 days;
uint dist = dbrReceived > dbrToBuy ? dbrReceived - dbrToBuy : dbrToBuy - dbrReceived;
for(uint i; i < iterations; ++i){
uint newAmountIn = amountIn;
if(dbrReceived > dbrToBuy){
newAmountIn -= stepSize;
} else {
newAmountIn += stepSize;
}
uint newDbrReceived = curvePool.get_dy(dolaIndex, dbrIndex, newAmountIn);
uint newDbrToBuy = (newAmountIn + dolaBorrowAmount) * period / 365 days;
uint newDist = newDbrReceived > newDbrToBuy ? newDbrReceived - newDbrToBuy : newDbrToBuy - newDbrReceived;
if(newDist < dist){
dbrReceived = newDbrReceived;
dbrToBuy = newDbrToBuy;
dist = newDist;
amountIn = newAmountIn;
}
stepSize /= 2;
}
return (amountIn, (dolaBorrowAmount + amountIn) * period / 365 days);
}
/**
@notice Sets a new curve pool
@dev Can only be called by the gov
@param _pool Address of the new curve pool
@param _dolaIndex Index of DOLA in the new curve pool
@param _dbrIndex Index of DBR in the new curve pool
*/
function setCurvePool(address _pool, uint256 _dolaIndex, uint256 _dbrIndex) external onlyGov {
ICurvePool newPool = ICurvePool(_pool);
require(newPool.coins(_dolaIndex) == address(DOLA), "Wrong dola index");
require(newPool.coins(_dbrIndex) == address(DBR), "Wrong dbr index");
DOLA.approve(address(curvePool), 0);
DBR.approve(address(curvePool), 0);
curvePool = newPool;
DOLA.approve(_pool, type(uint).max);
DBR.approve(_pool, type(uint).max);
dolaIndex = _dolaIndex;
dbrIndex = _dbrIndex;
emit NewCurvePool(_pool, _dolaIndex, _dbrIndex);
}
}
ReentrancyGuard.sol 87 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the 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);
}
SafeERC20.sol 173 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
Market.sol 688 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "src/interfaces/IERC20.sol";
// Caution. We assume all failed transfers cause reverts and ignore the returned bool.
interface IOracle {
function getPrice(address,uint) external returns (uint);
function viewPrice(address,uint) external view returns (uint);
}
interface IEscrow {
function initialize(IERC20 _token, address beneficiary) external;
function onDeposit() external;
function pay(address recipient, uint amount) external;
function balance() external view returns (uint);
}
interface IDolaBorrowingRights {
function onBorrow(address user, uint additionalDebt) external;
function onRepay(address user, uint repaidDebt) external;
function onForceReplenish(address user, address replenisher, uint amount, uint replenisherReward) external;
function balanceOf(address user) external view returns (uint);
function deficitOf(address user) external view returns (uint);
function replenishmentPriceBps() external view returns (uint);
}
interface IBorrowController {
function borrowAllowed(address msgSender, address borrower, uint amount) external returns (bool);
function onRepay(uint amount) external;
}
contract Market {
address public gov;
address public lender;
address public pauseGuardian;
address public immutable escrowImplementation;
IDolaBorrowingRights public immutable dbr;
IBorrowController public borrowController;
IERC20 public immutable dola = IERC20(0x865377367054516e17014CcdED1e7d814EDC9ce4);
IERC20 public immutable collateral;
IOracle public oracle;
uint public collateralFactorBps;
uint public replenishmentIncentiveBps;
uint public liquidationIncentiveBps;
uint public liquidationFeeBps;
uint public liquidationFactorBps = 5000; // 50% by default
bool immutable callOnDepositCallback;
bool public borrowPaused;
uint public totalDebt;
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping (address => IEscrow) public escrows; // user => escrow
mapping (address => uint) public debts; // user => debt
mapping(address => uint256) public nonces; // user => nonce
constructor (
address _gov,
address _lender,
address _pauseGuardian,
address _escrowImplementation,
IDolaBorrowingRights _dbr,
IERC20 _collateral,
IOracle _oracle,
uint _collateralFactorBps,
uint _replenishmentIncentiveBps,
uint _liquidationIncentiveBps,
bool _callOnDepositCallback
) {
require(_collateralFactorBps < 10000, "Invalid collateral factor");
require(_liquidationIncentiveBps > 0 && _liquidationIncentiveBps < 10000, "Invalid liquidation incentive");
require(_replenishmentIncentiveBps < 10000, "Replenishment incentive must be less than 100%");
gov = _gov;
lender = _lender;
pauseGuardian = _pauseGuardian;
escrowImplementation = _escrowImplementation;
dbr = _dbr;
collateral = _collateral;
oracle = _oracle;
collateralFactorBps = _collateralFactorBps;
replenishmentIncentiveBps = _replenishmentIncentiveBps;
liquidationIncentiveBps = _liquidationIncentiveBps;
callOnDepositCallback = _callOnDepositCallback;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
if(collateralFactorBps > 0){
uint unsafeLiquidationIncentive = (10000 - collateralFactorBps) * (liquidationFeeBps + 10000) / collateralFactorBps;
require(liquidationIncentiveBps < unsafeLiquidationIncentive, "Liquidation param allow profitable self liquidation");
}
}
modifier onlyGov {
require(msg.sender == gov, "Only gov can call this function");
_;
}
modifier liquidationParamChecker {
_;
if(collateralFactorBps > 0){
uint unsafeLiquidationIncentive = (10000 - collateralFactorBps) * (liquidationFeeBps + 10000) / collateralFactorBps;
require(liquidationIncentiveBps < unsafeLiquidationIncentive, "New liquidation param allow profitable self liquidation");
}
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("DBR MARKET")),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/**
@notice sets the oracle to a new oracle. Only callable by governance.
@param _oracle The new oracle conforming to the IOracle interface.
*/
function setOracle(IOracle _oracle) public onlyGov { oracle = _oracle; }
/**
@notice sets the borrow controller to a new borrow controller. Only callable by governance.
@param _borrowController The new borrow controller conforming to the IBorrowController interface.
*/
function setBorrowController(IBorrowController _borrowController) public onlyGov { borrowController = _borrowController; }
/**
@notice sets the address of governance. Only callable by governance.
@param _gov Address of the new governance.
*/
function setGov(address _gov) public onlyGov { gov = _gov; }
/**
@notice sets the lender to a new lender. The lender is allowed to recall dola from the contract. Only callable by governance.
@param _lender Address of the new lender.
*/
function setLender(address _lender) public onlyGov { lender = _lender; }
/**
@notice sets the pause guardian. The pause guardian can pause borrowing. Only callable by governance.
@param _pauseGuardian Address of the new pauseGuardian.
*/
function setPauseGuardian(address _pauseGuardian) public onlyGov { pauseGuardian = _pauseGuardian; }
/**
@notice sets the Collateral Factor requirement of the market as measured in basis points. 1 = 0.01%. Only callable by governance.
@dev Collateral factor mus be set below 100%
@param _collateralFactorBps The new collateral factor as measured in basis points.
*/
function setCollateralFactorBps(uint _collateralFactorBps) public onlyGov liquidationParamChecker {
require(_collateralFactorBps < 10000, "Invalid collateral factor");
collateralFactorBps = _collateralFactorBps;
}
/**
@notice sets the Liquidation Factor of the market as denoted in basis points.
The liquidation Factor denotes the maximum amount of debt that can be liquidated in basis points.
At 5000, 50% of of a borrower's underwater debt can be liquidated. Only callable by governance.
@dev Must be set between 1 and 10000.
@param _liquidationFactorBps The new liquidation factor in basis points. 1 = 0.01%/
*/
function setLiquidationFactorBps(uint _liquidationFactorBps) public onlyGov {
require(_liquidationFactorBps > 0 && _liquidationFactorBps <= 10000, "Invalid liquidation factor");
liquidationFactorBps = _liquidationFactorBps;
}
/**
@notice sets the Replenishment Incentive of the market as denoted in basis points.
The Replenishment Incentive is the percentage paid out to replenishers on a successful forceReplenish call, denoted in basis points.
@dev Must be set between 1 and 10000.
@param _replenishmentIncentiveBps The new replenishment incentive set in basis points. 1 = 0.01%
*/
function setReplenismentIncentiveBps(uint _replenishmentIncentiveBps) public onlyGov {
require(_replenishmentIncentiveBps < 10000, "Invalid replenishment incentive");
replenishmentIncentiveBps = _replenishmentIncentiveBps;
}
/**
@notice sets the Liquidation Incentive of the market as denoted in basis points.
The Liquidation Incentive is the percentage paid out to liquidators of a borrower's debt when successfully liquidated.
@dev Must be set between 0 and 10000 - liquidation fee.
@param _liquidationIncentiveBps The new liqudation incentive set in basis points. 1 = 0.01%
*/
function setLiquidationIncentiveBps(uint _liquidationIncentiveBps) public onlyGov liquidationParamChecker {
require(_liquidationIncentiveBps > 0 && _liquidationIncentiveBps + liquidationFeeBps < 10000, "Invalid liquidation incentive");
liquidationIncentiveBps = _liquidationIncentiveBps;
}
/**
@notice sets the Liquidation Fee of the market as denoted in basis points.
The Liquidation Fee is the percentage paid out to governance of a borrower's debt when successfully liquidated.
@dev Must be set between 0 and 10000 - liquidation factor.
@param _liquidationFeeBps The new liquidation fee set in basis points. 1 = 0.01%
*/
function setLiquidationFeeBps(uint _liquidationFeeBps) public onlyGov liquidationParamChecker {
require(_liquidationFeeBps + liquidationIncentiveBps < 10000, "Invalid liquidation fee");
liquidationFeeBps = _liquidationFeeBps;
}
/**
@notice Recalls amount of DOLA to the lender.
@param amount The amount od DOLA to recall to the the lender.
*/
function recall(uint amount) public {
require(msg.sender == lender, "Only lender can recall");
dola.transfer(msg.sender, amount);
}
/**
@notice Pauses or unpauses borrowing for the market. Only gov can unpause a market, while gov and pauseGuardian can pause it.
@param _value Boolean representing the state pause state of borrows. true = paused, false = unpaused.
*/
function pauseBorrows(bool _value) public {
if(_value) {
require(msg.sender == pauseGuardian || msg.sender == gov, "Only pause guardian or governance can pause");
} else {
require(msg.sender == gov, "Only governance can unpause");
}
borrowPaused = _value;
}
/**
@notice Internal function for creating an escrow for users to deposit collateral in.
@dev Uses create2 and minimal proxies to create the escrow at a deterministic address
@param user The address of the user to create an escrow for.
*/
function createEscrow(address user) internal returns (IEscrow instance) {
address implementation = escrowImplementation;
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, implementation))
mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
instance := create2(0, ptr, 0x37, user)
}
require(instance != IEscrow(address(0)), "ERC1167: create2 failed");
emit CreateEscrow(user, address(instance));
}
/**
@notice Internal function for getting the escrow of a user.
@dev If the escrow doesn't exist, an escrow contract is deployed.
@param user The address of the user owning the escrow.
*/
function getEscrow(address user) internal returns (IEscrow) {
if(escrows[user] != IEscrow(address(0))) return escrows[user];
IEscrow escrow = createEscrow(user);
escrow.initialize(collateral, user);
escrows[user] = escrow;
return escrow;
}
/**
@notice Deposit amount of collateral into escrow
@dev Will deposit the amount into the escrow contract.
@param amount Amount of collateral token to deposit.
*/
function deposit(uint amount) public {
deposit(msg.sender, amount);
}
/**
@notice Deposit and borrow in a single transaction.
@param amountDeposit Amount of collateral token to deposit into escrow.
@param amountBorrow Amount of DOLA to borrow.
*/
function depositAndBorrow(uint amountDeposit, uint amountBorrow) public {
deposit(amountDeposit);
borrow(amountBorrow);
}
/**
@notice Deposit amount of collateral into escrow on behalf of msg.sender
@dev Will deposit the amount into the escrow contract.
@param user User to deposit on behalf of.
@param amount Amount of collateral token to deposit.
*/
function deposit(address user, uint amount) public {
IEscrow escrow = getEscrow(user);
collateral.transferFrom(msg.sender, address(escrow), amount);
if(callOnDepositCallback) {
escrow.onDeposit();
}
emit Deposit(user, amount);
}
/**
@notice View function for predicting the deterministic escrow address of a user.
@dev Only use deposit() function for deposits and NOT the predicted escrow address unless you know what you're doing
@param user Address of the user owning the escrow.
*/
function predictEscrow(address user) public view returns (IEscrow predicted) {
address implementation = escrowImplementation;
address deployer = address(this);
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, implementation))
mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000)
mstore(add(ptr, 0x38), shl(0x60, deployer))
mstore(add(ptr, 0x4c), user)
mstore(add(ptr, 0x6c), keccak256(ptr, 0x37))
predicted := keccak256(add(ptr, 0x37), 0x55)
}
}
/**
@notice View function for getting the dollar value of the user's collateral in escrow for the market.
@param user Address of the user.
*/
function getCollateralValue(address user) public view returns (uint) {
IEscrow escrow = predictEscrow(user);
uint collateralBalance = escrow.balance();
return collateralBalance * oracle.viewPrice(address(collateral), collateralFactorBps) / 1 ether;
}
/**
@notice Internal function for getting the dollar value of the user's collateral in escrow for the market.
@dev Updates the lowest price comparisons of the pessimistic oracle
@param user Address of the user.
*/
function getCollateralValueInternal(address user) internal returns (uint) {
IEscrow escrow = predictEscrow(user);
uint collateralBalance = escrow.balance();
return collateralBalance * oracle.getPrice(address(collateral), collateralFactorBps) / 1 ether;
}
/**
@notice View function for getting the credit limit of a user.
@dev To calculate the available credit, subtract user debt from credit limit.
@param user Address of the user.
*/
function getCreditLimit(address user) public view returns (uint) {
uint collateralValue = getCollateralValue(user);
return collateralValue * collateralFactorBps / 10000;
}
/**
@notice Internal function for getting the credit limit of a user.
@dev To calculate the available credit, subtract user debt from credit limit. Updates the pessimistic oracle.
@param user Address of the user.
*/
function getCreditLimitInternal(address user) internal returns (uint) {
uint collateralValue = getCollateralValueInternal(user);
return collateralValue * collateralFactorBps / 10000;
}
/**
@notice Internal function for getting the withdrawal limit of a user.
The withdrawal limit is how much collateral a user can withdraw before their loan would be underwater. Updates the pessimistic oracle.
@param user Address of the user.
*/
function getWithdrawalLimitInternal(address user) internal returns (uint) {
IEscrow escrow = predictEscrow(user);
uint collateralBalance = escrow.balance();
if(collateralBalance == 0) return 0;
uint debt = debts[user];
if(debt == 0) return collateralBalance;
if(collateralFactorBps == 0) return 0;
uint minimumCollateral = debt * 1 ether / oracle.getPrice(address(collateral), collateralFactorBps) * 10000 / collateralFactorBps;
if(collateralBalance <= minimumCollateral) return 0;
return collateralBalance - minimumCollateral;
}
/**
@notice View function for getting the withdrawal limit of a user.
The withdrawal limit is how much collateral a user can withdraw before their loan would be underwater.
@param user Address of the user.
*/
function getWithdrawalLimit(address user) public view returns (uint) {
IEscrow escrow = predictEscrow(user);
uint collateralBalance = escrow.balance();
if(collateralBalance == 0) return 0;
uint debt = debts[user];
if(debt == 0) return collateralBalance;
if(collateralFactorBps == 0) return 0;
uint minimumCollateral = debt * 1 ether / oracle.viewPrice(address(collateral), collateralFactorBps) * 10000 / collateralFactorBps;
if(collateralBalance <= minimumCollateral) return 0;
return collateralBalance - minimumCollateral;
}
/**
@notice Internal function for borrowing DOLA against collateral.
@dev This internal function is shared between the borrow and borrowOnBehalf function
@param borrower The address of the borrower that debt will be accrued to.
@param to The address that will receive the borrowed DOLA
@param amount The amount of DOLA to be borrowed
*/
function borrowInternal(address borrower, address to, uint amount) internal {
require(!borrowPaused, "Borrowing is paused");
if(borrowController != IBorrowController(address(0))) {
require(borrowController.borrowAllowed(msg.sender, borrower, amount), "Denied by borrow controller");
}
uint credit = getCreditLimitInternal(borrower);
debts[borrower] += amount;
require(credit >= debts[borrower], "Exceeded credit limit");
totalDebt += amount;
dbr.onBorrow(borrower, amount);
dola.transfer(to, amount);
emit Borrow(borrower, amount);
}
/**
@notice Function for borrowing DOLA.
@dev Will borrow to msg.sender
@param amount The amount of DOLA to be borrowed.
*/
function borrow(uint amount) public {
borrowInternal(msg.sender, msg.sender, amount);
}
/**
@notice Function for using a signed message to borrow on behalf of an address owning an escrow with collateral.
@dev Signed messaged can be invalidated by incrementing the nonce. Will always borrow to the msg.sender.
@param from The address of the user being borrowed from
@param amount The amount to be borrowed
@param deadline Timestamp after which the signed message will be invalid
@param v The v param of the ECDSA signature
@param r The r param of the ECDSA signature
@param s The s param of the ECDSA signature
*/
function borrowOnBehalf(address from, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s) public {
require(deadline >= block.timestamp, "DEADLINE_EXPIRED");
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"BorrowOnBehalf(address caller,address from,uint256 amount,uint256 nonce,uint256 deadline)"
),
msg.sender,
from,
amount,
nonces[from]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == from, "INVALID_SIGNER");
borrowInternal(from, msg.sender, amount);
}
}
/**
@notice Internal function for withdrawing from the escrow
@dev The internal function is shared by the withdraw function and withdrawOnBehalf function
@param from The address owning the escrow to withdraw from.
@param to The address receiving the tokens
@param amount The amount being withdrawn.
*/
function withdrawInternal(address from, address to, uint amount) internal {
uint limit = getWithdrawalLimitInternal(from);
require(limit >= amount, "Insufficient withdrawal limit");
require(dbr.deficitOf(from) == 0, "Can't withdraw with DBR deficit");
IEscrow escrow = getEscrow(from);
escrow.pay(to, amount);
emit Withdraw(from, to, amount);
}
/**
@notice Function for withdrawing to msg.sender.
@param amount Amount to withdraw.
*/
function withdraw(uint amount) public {
withdrawInternal(msg.sender, msg.sender, amount);
}
/**
@notice Function for withdrawing maximum allowed to msg.sender.
@dev Useful for use with escrows that continously compound tokens, so there won't be dust amounts left
@dev Dangerous to use when the user has any amount of debt!
*/
function withdrawMax() public {
withdrawInternal(msg.sender, msg.sender, getWithdrawalLimitInternal(msg.sender));
}
/**
@notice Function for using a signed message to withdraw on behalf of an address owning an escrow with collateral.
@dev Signed messaged can be invalidated by incrementing the nonce. Will always withdraw to the msg.sender.
@param from The address of the user owning the escrow being withdrawn from
@param amount The amount to be withdrawn
@param deadline Timestamp after which the signed message will be invalid
@param v The v param of the ECDSA signature
@param r The r param of the ECDSA signature
@param s The s param of the ECDSA signature
*/
function withdrawOnBehalf(address from, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s) public {
require(deadline >= block.timestamp, "DEADLINE_EXPIRED");
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"WithdrawOnBehalf(address caller,address from,uint256 amount,uint256 nonce,uint256 deadline)"
),
msg.sender,
from,
amount,
nonces[from]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == from, "INVALID_SIGNER");
withdrawInternal(from, msg.sender, amount);
}
}
/**
@notice Function for using a signed message to withdraw on behalf of an address owning an escrow with collateral.
@dev Signed messaged can be invalidated by incrementing the nonce. Will always withdraw to the msg.sender.
@dev Useful for use with escrows that continously compound tokens, so there won't be dust amounts left
@dev Dangerous to use when the user has any amount of debt!
@param from The address of the user owning the escrow being withdrawn from
@param deadline Timestamp after which the signed message will be invalid
@param v The v param of the ECDSA signature
@param r The r param of the ECDSA signature
@param s The s param of the ECDSA signature
*/
function withdrawMaxOnBehalf(address from, uint deadline, uint8 v, bytes32 r, bytes32 s) public {
require(deadline >= block.timestamp, "DEADLINE_EXPIRED");
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"WithdrawMaxOnBehalf(address caller,address from,uint256 nonce,uint256 deadline)"
),
msg.sender,
from,
nonces[from]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == from, "INVALID_SIGNER");
withdrawInternal(from, msg.sender, getWithdrawalLimitInternal(from));
}
}
/**
@notice Function for incrementing the nonce of the msg.sender, making their latest signed message unusable.
*/
function invalidateNonce() public {
nonces[msg.sender]++;
}
/**
@notice Function for repaying debt on behalf of user. Debt must be repaid in DOLA.
@dev If the user has a DBR deficit, they risk initial debt being accrued by forced replenishments.
@param user Address of the user whose debt is being repaid
@param amount DOLA amount to be repaid. If set to max uint debt will be repaid in full.
*/
function repay(address user, uint amount) public {
uint debt = debts[user];
if(amount == type(uint).max){
amount = debt;
}
require(debt >= amount, "Repayment greater than debt");
debts[user] -= amount;
totalDebt -= amount;
dbr.onRepay(user, amount);
if(address(borrowController) != address(0)){
borrowController.onRepay(amount);
}
dola.transferFrom(msg.sender, address(this), amount);
emit Repay(user, msg.sender, amount);
}
/**
@notice Bundles repayment and withdrawal into a single function call.
@param repayAmount Amount of DOLA to be repaid
@param withdrawAmount Amount of underlying to be withdrawn from the escrow
*/
function repayAndWithdraw(uint repayAmount, uint withdrawAmount) public {
repay(msg.sender, repayAmount);
withdraw(withdrawAmount);
}
/**
@notice Function for forcing a user to replenish their DBR deficit at a pre-determined price.
The replenishment will accrue additional DOLA debt.
On a successful call, the caller will be paid a replenishment incentive.
@dev The function will only top the user back up to 0, meaning that the user will have a DBR deficit again in the next block.
@param user The address of the user being forced to replenish DBR
@param amount The amount of DBR the user will be replenished.
*/
function forceReplenish(address user, uint amount) public {
uint deficit = dbr.deficitOf(user);
require(deficit > 0, "No DBR deficit");
require(deficit >= amount, "Amount > deficit");
uint replenishmentCost = amount * dbr.replenishmentPriceBps() / 10000;
uint replenisherReward = replenishmentCost * replenishmentIncentiveBps / 10000;
debts[user] += replenishmentCost;
uint collateralValue = getCollateralValueInternal(user) * (10000 - liquidationIncentiveBps - liquidationFeeBps) / 10000;
require(collateralValue >= debts[user], "Exceeded collateral value");
totalDebt += replenishmentCost;
dbr.onForceReplenish(user, msg.sender, amount, replenisherReward);
dola.transfer(msg.sender, replenisherReward);
}
/**
@notice Function for forcing a user to replenish all of their DBR deficit at a pre-determined price.
The replenishment will accrue additional DOLA debt.
On a successful call, the caller will be paid a replenishment incentive.
@dev The function will only top the user back up to 0, meaning that the user will have a DBR deficit again in the next block.
@param user The address of the user being forced to replenish DBR
*/
function forceReplenishAll(address user) public {
uint deficit = dbr.deficitOf(user);
forceReplenish(user, deficit);
}
/**
@notice Function for liquidating a user's under water debt. Debt is under water when the value of a user's debt is above their collateral factor.
@param user The user to be liquidated
@param repaidDebt Th amount of user user debt to liquidate.
*/
function liquidate(address user, uint repaidDebt) public {
require(repaidDebt > 0, "Must repay positive debt");
uint debt = debts[user];
require(getCreditLimitInternal(user) < debt, "User debt is healthy");
require(repaidDebt <= debt * liquidationFactorBps / 10000, "Exceeded liquidation factor");
uint price = oracle.getPrice(address(collateral), collateralFactorBps);
uint liquidatorReward = repaidDebt * 1 ether / price;
liquidatorReward += liquidatorReward * liquidationIncentiveBps / 10000;
debts[user] -= repaidDebt;
totalDebt -= repaidDebt;
dbr.onRepay(user, repaidDebt);
if(address(borrowController) != address(0)){
borrowController.onRepay(repaidDebt);
}
dola.transferFrom(msg.sender, address(this), repaidDebt);
IEscrow escrow = predictEscrow(user);
escrow.pay(msg.sender, liquidatorReward);
if(liquidationFeeBps > 0) {
uint liquidationFee = repaidDebt * 1 ether / price * liquidationFeeBps / 10000;
uint balance = escrow.balance();
if(balance >= liquidationFee) {
escrow.pay(gov, liquidationFee);
} else if(balance > 0) {
escrow.pay(gov, balance);
}
}
emit Liquidate(user, msg.sender, repaidDebt, liquidatorReward);
}
event Deposit(address indexed account, uint amount);
event Borrow(address indexed account, uint amount);
event Withdraw(address indexed account, address indexed to, uint amount);
event Repay(address indexed account, address indexed repayer, uint amount);
event Liquidate(address indexed account, address indexed liquidator, uint repaidDebt, uint liquidatorReward);
event CreateEscrow(address indexed user, address escrow);
}
OffchainAbstractHelper.sol 363 lines
pragma solidity ^0.8.13;
import "../interfaces/IMarket.sol";
interface IERC20 {
function transfer(address to, uint amount) external;
function transferFrom(address from, address to, uint amount) external;
function approve(address to, uint amount) external;
function balanceOf(address user) external view returns(uint);
}
interface IWETH is IERC20 {
function withdraw(uint wad) external;
function deposit() external payable;
}
abstract contract OffchainAbstractHelper {
IERC20 constant DOLA = IERC20(0x865377367054516e17014CcdED1e7d814EDC9ce4);
IERC20 constant DBR = IERC20(0xAD038Eb671c44b853887A7E32528FaB35dC5D710);
IWETH constant WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
/**
Virtual functions implemented by the AMM interfacing part of the Helper contract
*/
/**
@notice Buys DBR for an amount of Dola
@param amount Amount of Dola to spend on DBR
@param minOut minimum amount of DBR to receive
@param receiver Address to send purchased DBR to
*/
function _buyDbr(uint amount, uint minOut, address receiver) internal virtual;
/**
@notice Sells an exact amount of DBR for DOLA
@param amount Amount of DBR to sell
@param minOut minimum amount of DOLA to receive
@param receiver Address to send purchased DOLA to
*/
function _sellDbr(uint amount, uint minOut, address receiver) internal virtual;
/**
@notice Approximates the amount of additional DOLA and DBR needed to sustain dolaBorrowAmount over the period
@dev Larger number of iterations increases both accuracy of the approximation and gas cost. This method should not be called in smart contract code.
@param dolaBorrowAmount The amount of DOLA the user wishes to borrow before covering DBR expenses
@param minDbr The amount of seconds the user wish to borrow the DOLA for
@param iterations The amount of approximation iterations.
@return Tuple of (dolaNeeded, dbrNeeded) representing the total dola needed to pay for the DBR and pay out dolaBorrowAmount and the dbrNeeded to sustain the loan over the period
*/
function approximateDolaAndDbrNeeded(uint dolaBorrowAmount, uint minDbr, uint iterations) public view virtual returns(uint, uint);
/**
@notice Borrows on behalf of the caller, buying the necessary DBR to pay for the loan over the period, by borrowing aditional funds to pay for the necessary DBR
@dev Has to borrow the dolaForDbr amount due to how the market's borrowOnBehalf functions, and repay the excess at the end of the call resulting in a weird repay event
@param market Market the caller wishes to borrow from
@param dolaAmount Amount the caller wants to end up with at their disposal
@param dolaForDbr The max amount of debt the caller is willing to end up with
This is a sensitive parameter and should be reasonably low to prevent sandwhiching.
A good estimate can be calculated given the approximateDolaAndDbrNeeded function, though should be set slightly higher.
@param minDbr The minDbr the caller wish to borrow for
@param deadline Deadline of the signature
@param v V parameter of the signature
@param r R parameter of the signature
@param s S parameter of the signature
*/
function buyDbrAndBorrowOnBehalf(
IMarket market,
uint dolaAmount,
uint dolaForDbr,
uint minDbr,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s)
public
{
//Borrow Dola
uint totalBorrow = dolaAmount + dolaForDbr;
market.borrowOnBehalf(msg.sender, totalBorrow, deadline, v, r, s);
//Buy DBR
_buyDbr(dolaForDbr, minDbr, msg.sender);
//Transfer remaining DOLA amount to user
DOLA.transfer(msg.sender, dolaAmount);
}
/**
@notice Deposits collateral and borrows on behalf of the caller, buying the necessary DBR to pay for the loan over the period, by borrowing aditional funds to pay for the necessary DBR
@dev Has to borrow the dolaForDbr amount due to how the market's borrowOnBehalf functions, and repay the excess at the end of the call resulting in a weird repay event
@param market Market the caller wish to deposit to and borrow from
@param dolaAmount Amount the caller wants to end up with at their disposal
@param dolaForDbr The max amount of debt the caller is willing to take on to buy dbr
This is a sensitive parameter and should be reasonably low to prevent sandwhiching.
A good estimate can be calculated given the approximateDolaAndDbrNeeded function, though should be set slightly higher.
@param minDbr The minDbr the caller wish to borrow for
@param deadline Deadline of the signature
@param v V parameter of the signature
@param r R parameter of the signature
@param s S parameter of the signature
*/
function depositBuyDbrAndBorrowOnBehalf(
IMarket market,
uint collateralAmount,
uint dolaAmount,
uint dolaForDbr,
uint minDbr,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s)
public
{
IERC20 collateral = IERC20(market.collateral());
//Deposit collateral
collateral.transferFrom(msg.sender, address(this), collateralAmount);
collateral.approve(address(market), collateralAmount);
market.deposit(msg.sender, collateralAmount);
//Borrow dola and buy dbr
buyDbrAndBorrowOnBehalf(market, dolaAmount, dolaForDbr, minDbr, deadline, v, r , s);
}
/**
@notice Deposits native eth as collateral and borrows on behalf of the caller,
buying the necessary DBR to pay for the loan over the period, by borrowing aditional funds to pay for the necessary DBR
@dev Has to borrow the dolaForDbr amount due to how the market's borrowOnBehalf functions, and repay the excess at the end of the call resulting in a weird repay event
@param market Market the caller wish to deposit to and borrow from
@param dolaAmount Amount the caller wants to end up with at their disposal
@param dolaForDbr The max amount of debt the caller is willing to end up with
This is a sensitive parameter and should be reasonably low to prevent sandwhiching.
A good estimate can be calculated given the approximateDolaAndDbrNeeded function, though should be set slightly higher.
@param minDbr The minDbr the caller wish to borrow for
@param deadline Deadline of the signature
@param v V parameter of the signature
@param r R parameter of the signature
@param s S parameter of the signature
*/
function depositNativeEthBuyDbrAndBorrowOnBehalf(
IMarket market,
uint dolaAmount,
uint dolaForDbr,
uint minDbr,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s)
public payable
{
IERC20 collateral = IERC20(market.collateral());
require(address(collateral) == address(WETH), "Market is not an ETH market");
WETH.deposit{value:msg.value}();
//Deposit collateral
collateral.approve(address(market), msg.value);
market.deposit(msg.sender, msg.value);
//Borrow dola and buy dbr
buyDbrAndBorrowOnBehalf(market, dolaAmount, dolaForDbr, minDbr, deadline, v, r , s);
}
/**
@notice Sells DBR on behalf of the caller and uses the proceeds along with DOLA from the caller to repay debt.
@dev The caller is unlikely to spend all of the DOLA they make available for the function call
@param market The market the user wishes to repay debt in
@param dolaAmount The maximum amount of dola debt the user is willing to repay
@param minDolaFromDbr The minimum amount of DOLA the caller expects to get in return for selling their DBR.
This is a sensitive parameter and should be provided with reasonably low slippage to prevent sandwhiching.
@param dbrAmountToSell The amount of DBR the caller wishes to sell
*/
function sellDbrAndRepayOnBehalf(IMarket market, uint dolaAmount, uint minDolaFromDbr, uint dbrAmountToSell) public {
uint dbrBal = DBR.balanceOf(msg.sender);
//If user has less DBR than ordered, sell what's available
if(dbrAmountToSell > dbrBal){
DBR.transferFrom(msg.sender, address(this), dbrBal);
_sellDbr(dbrBal, minDolaFromDbr, address(this));
} else {
DBR.transferFrom(msg.sender, address(this), dbrAmountToSell);
_sellDbr(dbrAmountToSell, minDolaFromDbr, address(this));
}
uint debt = market.debts(msg.sender);
uint dolaBal = DOLA.balanceOf(address(this));
//If the debt is lower than the dolaAmount, repay debt else repay dolaAmount
uint repayAmount = debt < dolaAmount ? debt : dolaAmount;
//If dolaBal is less than repayAmount, transfer remaining DOLA from user, otherwise transfer excess dola to user
if(dolaBal < repayAmount){
DOLA.transferFrom(msg.sender, address(this), repayAmount - dolaBal);
} else {
DOLA.transfer(msg.sender, dolaBal - repayAmount);
}
//Repay repayAmount
DOLA.approve(address(market), repayAmount);
market.repay(msg.sender, repayAmount);
}
/**
@notice Sells DBR on behalf of the caller and uses the proceeds along with DOLA from the caller to repay debt, and then withdraws collateral
@dev The caller is unlikely to spend all of the DOLA they make available for the function call
@param market Market the user wishes to repay debt in
@param dolaAmount Maximum amount of dola debt the user is willing to repay
@param minDolaFromDbr Minimum amount of DOLA the caller expects to get in return for selling their DBR
This is a sensitive parameter and should be provided with reasonably low slippage to prevent sandwhiching.
@param dbrAmountToSell Amount of DBR the caller wishes to sell
@param collateralAmount Amount of collateral to withdraw
@param deadline Deadline of the signature
@param v V parameter of the signature
@param r R parameter of the signature
@param s S parameter of the signature
*/
function sellDbrRepayAndWithdrawOnBehalf(
IMarket market,
uint dolaAmount,
uint minDolaFromDbr,
uint dbrAmountToSell,
uint collateralAmount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s)
external
{
//Repay
sellDbrAndRepayOnBehalf(market, dolaAmount, minDolaFromDbr, dbrAmountToSell);
//Withdraw
market.withdrawOnBehalf(msg.sender, collateralAmount, deadline, v, r, s);
//Transfer collateral to msg.sender
IERC20(market.collateral()).transfer(msg.sender, collateralAmount);
}
/**
@notice Sells DBR on behalf of the caller and uses the proceeds along with DOLA from the caller to repay debt, and then withdraws collateral
@dev The caller is unlikely to spend all of the DOLA they make available for the function call
@param market Market the user wishes to repay debt in
@param dolaAmount Maximum amount of dola debt the user is willing to repay
@param minDolaFromDbr Minimum amount of DOLA the caller expects to get in return for selling their DBR
This is a sensitive parameter and should be provided with reasonably low slippage to prevent sandwhiching.
@param dbrAmountToSell Amount of DBR the caller wishes to sell
@param collateralAmount Amount of collateral to withdraw
@param deadline Deadline of the signature
@param v V parameter of the signature
@param r R parameter of the signature
@param s S parameter of the signature
*/
function sellDbrRepayAndWithdrawNativeEthOnBehalf(
IMarket market,
uint dolaAmount,
uint minDolaFromDbr,
uint dbrAmountToSell,
uint collateralAmount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s)
external
{
//Repay
sellDbrAndRepayOnBehalf(market, dolaAmount, minDolaFromDbr, dbrAmountToSell);
//Withdraw
withdrawNativeEthOnBehalf(market, collateralAmount, deadline, v, r, s);
}
/**
@notice Repays debt, and then withdraws native ETH
@dev The caller is unlikely to spend all of the DOLA they make available for the function call
@param market Market the user wishes to repay debt in
@param dolaAmount Amount of dola debt the user is willing to repay
@param collateralAmount Amount of collateral to withdraw
@param deadline Deadline of the signature
@param v V parameter of the signature
@param r R parameter of the signature
@param s S parameter of the signature
*/
function repayAndWithdrawNativeEthOnBehalf(
IMarket market,
uint dolaAmount,
uint collateralAmount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s)
external
{
// Repay
DOLA.transferFrom(msg.sender, address(this), dolaAmount);
DOLA.approve(address(market), dolaAmount);
market.repay(msg.sender, dolaAmount);
// Withdraw
withdrawNativeEthOnBehalf(market, collateralAmount, deadline, v, r, s);
}
/**
@notice Helper function for depositing native eth to WETH markets
@param market The WETH market to deposit to
*/
function depositNativeEthOnBehalf(IMarket market) public payable {
require(address(market.collateral()) == address(WETH), "Not an ETH market");
WETH.deposit{value:msg.value}();
WETH.approve(address(market), msg.value);
market.deposit(msg.sender, msg.value);
}
/**
@notice Helper function for depositing native eth to WETH markets before borrowing on behalf of the depositor
@param market The WETH market to deposit to
@param borrowAmount The amount to borrow on behalf of the depositor
@param deadline Deadline of the signature
@param v V parameter of the signature
@param r R parameter of the signature
@param s S parameter of the signature
*/
function depositNativeEthAndBorrowOnBehalf(IMarket market, uint borrowAmount, uint deadline, uint8 v, bytes32 r, bytes32 s) public payable {
require(address(market.collateral()) == address(WETH), "Not an ETH market");
//Deposit native eth
WETH.deposit{value:msg.value}();
WETH.approve(address(market), msg.value);
market.deposit(msg.sender, msg.value);
//Borrow Dola
market.borrowOnBehalf(msg.sender, borrowAmount, deadline, v, r, s);
DOLA.transfer(msg.sender, borrowAmount);
}
/**
@notice Helper function for withdrawing to native eth
@param market WETH market to withdraw collateral from
@param collateralAmount Amount of collateral to withdraw
@param deadline Deadline of the signature
@param v V parameter of the signature
@param r R parameter of the signature
@param s S parameter of the signature
*/
function withdrawNativeEthOnBehalf(
IMarket market,
uint collateralAmount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s)
public
{
market.withdrawOnBehalf(msg.sender, collateralAmount, deadline, v, r, s);
IERC20 collateral = IERC20(market.collateral());
require(address(collateral) == address(WETH), "Not an ETH market");
WETH.withdraw(collateralAmount);
(bool success,) = payable(msg.sender).call{value:collateralAmount}("");
require(success, "Failed to transfer ETH");
}
//Empty receive function for receiving the native eth sent by the WETH contract
receive() external payable {}
}
Ownable.sol 50 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
/**
* @title Ownable
* @notice This contract adds Ownable functionality to the contract
*/
contract Ownable {
error NotGov();
error NotPendingGov();
address public gov;
address public pendingGov;
event NewGov(address indexed gov);
event NewPendingGov(address indexed pendingGov);
/** @dev Constructor
@param _gov The address of Inverse Finance governance
**/
constructor(address _gov) {
gov = _gov;
}
modifier onlyGov() {
if (msg.sender != gov) revert NotGov();
_;
}
/**
* @notice Sets the pendingGov, which can claim gov role.
* @dev Only callable by gov
* @param _pendingGov The address of the pendingGov
*/
function setPendingGov(address _pendingGov) external onlyGov {
pendingGov = _pendingGov;
emit NewPendingGov(_pendingGov);
}
/**
* @notice Claims the gov role
* @dev Only callable by pendingGov
*/
function claimPendingGov() external {
if (msg.sender != pendingGov) revert NotPendingGov();
gov = pendingGov;
pendingGov = address(0);
emit NewGov(gov);
}
}
IERC1363.sol 86 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
Address.sol 151 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert Errors.FailedCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}
IERC20.sol 31 lines
pragma solidity ^0.8.13;
interface IERC20 {
function approve(address, uint) external;
function transfer(address, uint) external returns (bool);
function transferFrom(address, address, uint) external returns (bool);
function balanceOf(address) external view returns (uint);
function allowance(address from, address to) external view returns (uint);
function symbol() external view returns (string memory);
}
interface IMintable is IERC20 {
function mint(address, uint) external;
function burn(uint) external;
function addMinter(address minter) external;
}
interface IDelegateableERC20 is IERC20 {
function delegate(address delegatee) external;
function delegates(
address delegator
) external view returns (address delegatee);
}
IERC20.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
Errors.sol 26 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
Read Contract
CALLBACK_SUCCESS 0x8237e538 → bytes32
DELEVERAGE 0xb9181d1c → bytes32
LEVERAGE 0x8d01f0ba → bytes32
approximateDolaAndDbrNeeded 0xb8b71de1 → uint256, uint256
curvePool 0x218751b2 → address
dbrIndex 0x2ab9535a → uint256
dolaIndex 0x465c2447 → uint256
flash 0xd336c82d → address
gov 0x12d43a51 → address
isExchangeProxy 0xe0bfa258 → bool
markets 0x8e8f294b → address, address, address, bool
pendingGov 0x25240810 → address
Write Contract 21 functions
These functions modify contract state and require a wallet transaction to execute.
allowProxy 0x76daa13e
address _proxy
buyDbrAndBorrowOnBehalf 0xe4cb99c0
address market
uint256 dolaAmount
uint256 dolaForDbr
uint256 minDbr
uint256 deadline
uint8 v
bytes32 r
bytes32 s
claimPendingGov 0xf0c9e465
No parameters
deleveragePosition 0x2c75b590
uint256 value
address market
address exchangeProxy
uint256 collateralAmount
bytes swapCallData
tuple permit
bytes helperData
tuple dbrData
denyProxy 0x0715940e
address _proxy
depositAndLeveragePosition 0x33b19eda
uint256 initialDeposit
uint256 value
address market
address exchangeProxy
bytes swapCallData
tuple permit
bytes helperData
tuple dbrData
bool depositCollateral
depositBuyDbrAndBorrowOnBehalf 0x4879539f
address market
uint256 collateralAmount
uint256 dolaAmount
uint256 dolaForDbr
uint256 minDbr
uint256 deadline
uint8 v
bytes32 r
bytes32 s
depositNativeEthAndBorrowOnBehalf 0x0d7fc7bc
address market
uint256 borrowAmount
uint256 deadline
uint8 v
bytes32 r
bytes32 s
depositNativeEthBuyDbrAndBorrowOnBehalf 0x82ab11b3
address market
uint256 dolaAmount
uint256 dolaForDbr
uint256 minDbr
uint256 deadline
uint8 v
bytes32 r
bytes32 s
depositNativeEthOnBehalf 0x1d0ef6c0
address market
leveragePosition 0x9818cb02
uint256 value
address market
address exchangeProxy
bytes swapCallData
tuple permit
bytes helperData
tuple dbrData
onFlashLoan 0x23e30c8b
address initiator
address
uint256 amount
uint256
bytes data
returns: bytes32
repayAndWithdrawNativeEthOnBehalf 0xb86cb7cb
address market
uint256 dolaAmount
uint256 collateralAmount
uint256 deadline
uint8 v
bytes32 r
bytes32 s
sellDbrAndRepayOnBehalf 0x459be986
address market
uint256 dolaAmount
uint256 minDolaFromDbr
uint256 dbrAmountToSell
sellDbrRepayAndWithdrawNativeEthOnBehalf 0xb56bcd35
address market
uint256 dolaAmount
uint256 minDolaFromDbr
uint256 dbrAmountToSell
uint256 collateralAmount
uint256 deadline
uint8 v
bytes32 r
bytes32 s
sellDbrRepayAndWithdrawOnBehalf 0x8e5fa59c
address market
uint256 dolaAmount
uint256 minDolaFromDbr
uint256 dbrAmountToSell
uint256 collateralAmount
uint256 deadline
uint8 v
bytes32 r
bytes32 s
setCurvePool 0xf9adbf11
address _pool
uint256 _dolaIndex
uint256 _dbrIndex
setMarket 0xdfc0791c
address _market
address _buySellToken
address _helper
bool useProxy
setPendingGov 0xefdf0bb0
address _pendingGov
updateMarketHelper 0xa92d64e0
address _market
address _helper
withdrawNativeEthOnBehalf 0x822c0889
address market
uint256 collateralAmount
uint256 deadline
uint8 v
bytes32 r
bytes32 s
Recent Transactions
No transactions found for this address