Address Contract Partially Verified
Address
0xBc50B57ef01F009A9097df4A921eA1b1AA0f0cFF
Balance
0 ETH
Nonce
1
Code Size
16132 bytes
Creator
0x37e6365d...70bF at tx 0x280069fd...03b93b
Indexed Transactions
0
Contract Bytecode
16132 bytes
0x60806040526004361061010d5760003560e01c80638b2704ec11610095578063c4ba5db211610064578063c4ba5db2146102b6578063c58bc7e0146102d6578063d1d719ef146102f6578063da0321cd14610316578063f875572d1461033e57610114565b80638b2704ec1461024c578063ac43070b14610261578063af6f220a14610281578063b7711c631461029657610114565b806364718c1d116100dc57806364718c1d146101a85780637452ed1e146101d55780637535d2461461020257806380b2edd814610217578063848af32d1461023757610114565b80630542975c146101195780631b11d0ff146101445780631d2d88ca14610171578063590c81c51461018657610114565b3661011457005b600080fd5b34801561012557600080fd5b5061012e610351565b60405161013b91906136d0565b60405180910390f35b34801561015057600080fd5b5061016461015f366004612ddd565b610375565b60405161013b91906136c5565b34801561017d57600080fd5b5061012e61046f565b34801561019257600080fd5b506101a66101a136600461303a565b610493565b005b3480156101b457600080fd5b506101c86101c3366004613113565b6104d6565b60405161013b9190613df5565b3480156101e157600080fd5b506101f56101f03660046130d2565b6106d5565b60405161013b9190613de7565b34801561020e57600080fd5b5061012e6106f2565b34801561022357600080fd5b506101a661023236600461301e565b610716565b34801561024357600080fd5b506101c8610722565b34801561025857600080fd5b5061012e610727565b34801561026d57600080fd5b506101a661027c366004612f66565b61074b565b34801561028d57600080fd5b5061012e61077f565b3480156102a257600080fd5b506101a66102b136600461301e565b6107a3565b3480156102c257600080fd5b506101c86102d1366004613113565b6107f5565b3480156102e257600080fd5b506101a66102f1366004613190565b6109ee565b34801561030257600080fd5b506101a661031136600461303a565b610a3e565b34801561032257600080fd5b5061032b610a74565b60405161013b97969594939291906135f7565b6101a661034c366004613113565b610aad565b7f0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e81565b6000336001600160a01b037f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e216146103c85760405162461bcd60e51b81526004016103bf90613bf7565b60405180910390fd5b6103d0612a64565b6103dc83850185613217565b90506001600160a01b03851630146104065760405162461bcd60e51b81526004016103bf90613743565b60408101516008546001600160a01b039081169116146104385760405162461bcd60e51b81526004016103bf90613743565b8060600151156104535761044e88888884610afc565b61045f565b61045f88888884610b59565b60019150505b9695505050505050565b7f0000000000000000000000009d08cced85a68bf8a19374ed4b5753ae3be9f74f81565b600260005414156104b65760405162461bcd60e51b81526004016103bf90613b40565b60026000556104c9868686868686610c23565b5050600160005550505050565b604051632961046560e21b81526000906001600160a01b037f0000000000000000000000009d08cced85a68bf8a19374ed4b5753ae3be9f74f169063a5841194906105259088906004016136d0565b600060405180830381600087803b15801561053f57600080fd5b505af1158015610553573d6000803e3d6000fd5b5050505061055f612ade565b61056b86866000610d55565b9050600061058e670de3e93f3bb0400083608001516111d590919063ffffffff16565b90506000733026eb2097468fcdc5387af73379948f8aa0a86063f943b31e600188856040518463ffffffff1660e01b81526004016105ce93929190613c5b565b60206040518083038186803b1580156105e657600080fd5b505af41580156105fa573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061e919061331a565b9050600061063982856040015161120890919063ffffffff16565b6040516301fae87160e61b8152909150733026eb2097468fcdc5387af73379948f8aa0a86090637eba1c4090610678906001908a908690600401613c5b565b60206040518083038186803b15801561069057600080fd5b505af41580156106a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c8919061331a565b9998505050505050505050565b6106dd612ade565b6106e8848484610d55565b90505b9392505050565b7f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e281565b61071f81611230565b50565b600281565b7f000000000000000000000000d2463675a099101e36d85278494268261a66603a81565b60005b815181101561077b5761077382828151811061076657fe5b6020026020010151611230565b60010161074e565b5050565b7f00000000000000000000000004b59f9f09750c044d7cfbc177561e409085f0f381565b6107ab612ade565b6107bf82670de0b6b3a76400006001610d55565b90506107ce8160000151611230565b6107db816020015161125d565b6107e88160600151611230565b61077b816060015161125d565b604051632961046560e21b81526000906001600160a01b037f0000000000000000000000009d08cced85a68bf8a19374ed4b5753ae3be9f74f169063a5841194906108449088906004016136d0565b600060405180830381600087803b15801561085e57600080fd5b505af1158015610872573d6000803e3d6000fd5b5050505061087e612ade565b61088a86866001610d55565b905060006108ad670de3e93f3bb0400083604001516111d590919063ffffffff16565b90506000733026eb2097468fcdc5387af73379948f8aa0a860637eba1c4060018886608001516040518463ffffffff1660e01b81526004016108f193929190613c5b565b60206040518083038186803b15801561090957600080fd5b505af415801561091d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610941919061331a565b9050610953828263ffffffff61120816565b604051637ca1d98f60e11b8152909250733026eb2097468fcdc5387af73379948f8aa0a8609063f943b31e906109929060019089908790600401613c5b565b60206040518083038186803b1580156109aa57600080fd5b505af41580156109be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109e2919061331a565b98975050505050505050565b60026000541415610a115760405162461bcd60e51b81526004016103bf90613b40565b6002600055610a328585600080516020613eaf833981519152868686610c23565b50506001600055505050565b60026000541415610a615760405162461bcd60e51b81526004016103bf90613b40565b60026000556104c986868686868661135f565b6001546002546003546004546005546006546007546001600160a01b039687169695861695948516949384169392831692918216911687565b60026000541415610ad05760405162461bcd60e51b81526004016103bf90613b40565b6002600055610af18484600080516020613eaf83398151915234868661135f565b505060016000555050565b610b068484611487565b610b1d816000015182602001518360400151611508565b6000610b2c858486018461158f565b90508160a00151811115610b525760405162461bcd60e51b81526004016103bf9061378b565b5050505050565b610b708160000151826020015183604001516116ad565b610b8d8160c001516020015160028360c001516040015103611718565b6000610bb2838501868460c00151604001518560c00151602001518660e001516117bf565b90506000610bf58284600001518560200151866040015187608001518860a001518960c0015160200151600280028b60c0015160400151038b6101000151611a0d565b90508260a00151811015610c1b5760405162461bcd60e51b81526004016103bf906136f7565b505050505050565b604051632961046560e21b81526001600160a01b037f0000000000000000000000009d08cced85a68bf8a19374ed4b5753ae3be9f74f169063a584119490610c6f9089906004016136d0565b600060405180830381600087803b158015610c8957600080fd5b505af1158015610c9d573d6000803e3d6000fd5b50505050610ca9612ade565b610cb587876000610d55565b90506060604051806101200160405280896001600160a01b03168152602001888152602001336001600160a01b03168152602001600015158152602001876001600160a01b0316815260200186815260200183815260200185815260200184815250604051602001610d279190613d39565b6040516020818303038152906040529050610d4b8260600151836080015183611aef565b5050505050505050565b610d5d612ade565b60608060608415610e165760405163131e26b960e01b81526001600160a01b037f00000000000000000000000004b59f9f09750c044d7cfbc177561e409085f0f3169063131e26b990610db6908a908a9060040161365c565b60006040518083038186803b158015610dce57600080fd5b505afa158015610de2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610e0a9190810190612e81565b91945092509050610ec0565b6040516335c729db60e11b81526001600160a01b037f00000000000000000000000004b59f9f09750c044d7cfbc177561e409085f0f31690636b8e53b690610e64908a908a9060040161365c565b60006040518083038186803b158015610e7c57600080fd5b505afa158015610e90573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610eb89190810190612e81565b919450925090505b8251600214610ee15760405162461bcd60e51b81526004016103bf906139a3565b81600081518110610eee57fe5b602002602001015160001480610f18575081600181518110610f0c57fe5b60200260200101516000145b610f345760405162461bcd60e51b81526004016103bf90613921565b80600081518110610f4157fe5b602002602001015160001480610f6b575080600181518110610f5f57fe5b60200260200101516000145b610f875760405162461bcd60e51b81526004016103bf90613b77565b600082600081518110610f9657fe5b602002602001015111156110c7576040518060a0016040528084600081518110610fbc57fe5b60200260200101516001600160a01b0316815260200184600081518110610fdf57fe5b60200260200101516001600160a01b031663b16a19de6040518163ffffffff1660e01b815260040160206040518083038186803b15801561101f57600080fd5b505afa158015611033573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110579190612dc1565b6001600160a01b0316815260200160028460008151811061107457fe5b60200260200101510181526020018460018151811061108f57fe5b60200260200101516001600160a01b03168152602001826001815181106110b257fe5b602002602001015181525093505050506106eb565b6040518060a00160405280846001815181106110df57fe5b60200260200101516001600160a01b031681526020018460018151811061110257fe5b60200260200101516001600160a01b031663b16a19de6040518163ffffffff1660e01b815260040160206040518083038186803b15801561114257600080fd5b505afa158015611156573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061117a9190612dc1565b6001600160a01b0316815260200160028460018151811061119757fe5b6020026020010151018152602001846000815181106111b257fe5b60200260200101516001600160a01b03168152602001826000815181106110b257fe5b60006111ff670de0b6b3a76400006111f3858563ffffffff611bc616565b9063ffffffff611c0016565b90505b92915050565b60008282111561122a5760405162461bcd60e51b81526004016103bf9061380d565b50900390565b61071f817f00000000000000000000000004b59f9f09750c044d7cfbc177561e409085f0f3600019611c32565b604051636eb1769f60e11b81526000906001600160a01b0383169063dd62ed3e906112ae9030907f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e290600401613594565b60206040518083038186803b1580156112c657600080fd5b505afa1580156112da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112fe919061331a565b9050801561133257611332827f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26000611c32565b61077b827f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2600019611c32565b604051632961046560e21b81526001600160a01b037f0000000000000000000000009d08cced85a68bf8a19374ed4b5753ae3be9f74f169063a5841194906113ab9089906004016136d0565b600060405180830381600087803b1580156113c557600080fd5b505af11580156113d9573d6000803e3d6000fd5b505050506113e5612ade565b6113f187876001610d55565b90506060604051806101200160405280896001600160a01b03168152602001888152602001336001600160a01b03168152602001600115158152602001876001600160a01b03168152602001868152602001838152602001858152602001848152506040516020016114639190613d39565b6040516020818303038152906040529050610d4b8260200151836040015183611aef565b60405163e8eda9df60e01b81526001600160a01b037f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2169063e8eda9df906114da90859085903090600090600401613698565b600060405180830381600087803b1580156114f457600080fd5b505af1158015610c1b573d6000803e3d6000fd5b6040516336bc7a3d60e11b81526001600160a01b037f00000000000000000000000004b59f9f09750c044d7cfbc177561e409085f0f31690636d78f47a9061155890869086908690600401613675565b600060405180830381600087803b15801561157257600080fd5b505af1158015611586573d6000803e3d6000fd5b50505050505050565b6000806115b2858460c00151606001518560c00151608001518660e00151611cde565b9050600060026115c8868463ffffffff61120816565b0190506000600080516020613eaf8339815191526001600160a01b031685608001516001600160a01b0316141561161a57611613878387604001518860a00151896101000151611ece565b905061163c565b6116398783876040015188608001518960a001518a6101000151611feb565b90505b84608001516001600160a01b031685600001516001600160a01b031686604001516001600160a01b03167f846f5655f4f8fa6ef5e4ad493e284e34854a83d000836d0493800c259ea9706584896020015160405161169b929190613dfe565b60405180910390a49695505050505050565b6116c86001600160a01b03841682308563ffffffff61207f16565b604051635c833bfd60e01b81526001600160a01b037f00000000000000000000000004b59f9f09750c044d7cfbc177561e409085f0f31690635c833bfd9061155890869086903090600401613675565b604051631a4ca37b60e21b81526001600160a01b037f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e216906369328dec9061176890859085903090600401613675565b602060405180830381600087803b15801561178257600080fd5b505af1158015611796573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117ba919061331a565b505050565b600081600001518386806001600160a01b0316826001600160a01b03161461191d57816001600160a01b0316836000815181106117f857fe5b60200260200101516001600160a01b0316148061186057506007546001600160a01b0383811691161480156118605750600080516020613eaf8339815191526001600160a01b03168360008151811061184d57fe5b60200260200101516001600160a01b0316145b61187c5760405162461bcd60e51b81526004016103bf906138a1565b806001600160a01b03168360018551038151811061189657fe5b60200260200101516001600160a01b0316148061190157506007546001600160a01b0382811691161480156119015750600080516020613eaf8339815191526001600160a01b0316836001855103815181106118ee57fe5b60200260200101516001600160a01b0316145b61191d5760405162461bcd60e51b81526004016103bf90613a75565b6040805160e0810182526001546001600160a01b03908116825260025481166020830152600354811682840152600480548216606084015260055482166080840152600654821660a084015260075490911660c08301529151636fd7d45f60e01b8152733026eb2097468fcdc5387af73379948f8aa0a86092636fd7d45f926119ae9290918e918d918c9101613c2c565b60206040518083038186803b1580156119c657600080fd5b505af41580156119da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119fe919061331a565b93505b50505095945050505050565b600089831015611a2f5760405162461bcd60e51b81526004016103bf906139e8565b6000611a41848c63ffffffff61120816565b905060006001600160a01b038816600080516020613eaf8339815191521415611a7857611a7186838b8a886120d7565b9050611a89565b611a8686838b8b8b896122ce565b90505b876001600160a01b03168b6001600160a01b03168a6001600160a01b03167f9c1558194024d73db1b6fc2739c3070cacc4598122100dd6f7d3a3dd8cee5f368d85604051611ad8929190613dfe565b60405180910390a49b9a5050505050505050505050565b6008546001600160a01b031615611b185760405162461bcd60e51b81526004016103bf90613bc0565b600880546001600160a01b031916331790556040516310ac2ddf60e21b81527f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e26001600160a01b0316906342b0b77c90611b7f9030908790879087906000906004016135ae565b600060405180830381600087803b158015611b9957600080fd5b505af1158015611bad573d6000803e3d6000fd5b5050600880546001600160a01b03191690555050505050565b600082611bd557506000611202565b82820282848281611be257fe5b04146111ff5760405162461bcd60e51b81526004016103bf90613a34565b6000808211611c215760405162461bcd60e51b81526004016103bf9061396c565b818381611c2a57fe5b049392505050565b604051636eb1769f60e11b81526000906001600160a01b0385169063dd62ed3e90611c639030908790600401613594565b60206040518083038186803b158015611c7b57600080fd5b505afa158015611c8f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cb3919061331a565b905081811015611cd857611cd86001600160a01b03851684831963ffffffff61232b16565b50505050565b600081600001518486806001600160a01b0316826001600160a01b031614611e3c57816001600160a01b031683600081518110611d1757fe5b60200260200101516001600160a01b03161480611d7f57506007546001600160a01b038381169116148015611d7f5750600080516020613eaf8339815191526001600160a01b031683600081518110611d6c57fe5b60200260200101516001600160a01b0316145b611d9b5760405162461bcd60e51b81526004016103bf906138a1565b806001600160a01b031683600185510381518110611db557fe5b60200260200101516001600160a01b03161480611e2057506007546001600160a01b038281169116148015611e205750600080516020613eaf8339815191526001600160a01b031683600185510381518110611e0d57fe5b60200260200101516001600160a01b0316145b611e3c5760405162461bcd60e51b81526004016103bf90613a75565b6040805160e0810182526001546001600160a01b03908116825260025481166020830152600354811682840152600480548216606084015260055482166080840152600654821660a084015260075490911660c08301529151630355f53b60e31b8152733026eb2097468fcdc5387af73379948f8aa0a86092631aafa9d8926109929290918b916000918c9101613c2c565b60075460408051630d0e30db60e41b815290516000926001600160a01b03169163d0e30db0918691600480820192879290919082900301818588803b158015611f1657600080fd5b505af1158015611f2a573d6000803e3d6000fd5b505060075460009350611f4d925089915088906001600160a01b031687876123dc565b905080841115610465576000611f69858363ffffffff61120816565b600754604051632e1a7d4d60e01b81529192506001600160a01b031690632e1a7d4d90611f9a908490600401613df5565b600060405180830381600087803b158015611fb457600080fd5b505af1158015611fc8573d6000803e3d6000fd5b50611fe0925050506001600160a01b038716826125ed565b509695505050505050565b6000866001600160a01b0316846001600160a01b0316141561201957612012878787612689565b5084610465565b6120346001600160a01b03851686308663ffffffff61207f16565b600061204388888787876123dc565b9050838110156120785761207886612061868463ffffffff61120816565b6001600160a01b038816919063ffffffff6126aa16565b9050610465565b611cd8846323b872dd60e01b8585856040516024016120a093929190613638565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526126c9565b80516007546000919087906001600160a01b03908116908216811461223257816001600160a01b03168360008151811061210d57fe5b60200260200101516001600160a01b0316148061217557506007546001600160a01b0383811691161480156121755750600080516020613eaf8339815191526001600160a01b03168360008151811061216257fe5b60200260200101516001600160a01b0316145b6121915760405162461bcd60e51b81526004016103bf906138a1565b806001600160a01b0316836001855103815181106121ab57fe5b60200260200101516001600160a01b0316148061221657506007546001600160a01b0382811691161480156122165750600080516020613eaf8339815191526001600160a01b03168360018551038151811061220357fe5b60200260200101516001600160a01b0316145b6122325760405162461bcd60e51b81526004016103bf90613a75565b600754600090612250908b908b906001600160a01b03168a8a612758565b905080156119fe57600754604051632e1a7d4d60e01b81526001600160a01b0390911690632e1a7d4d90612288908490600401613df5565b600060405180830381600087803b1580156122a257600080fd5b505af11580156122b6573d6000803e3d6000fd5b506119fe925050506001600160a01b038916826125ed565b6000866001600160a01b0316846001600160a01b031614156122f557612012878787612947565b60006123048888878787612758565b90506123206001600160a01b038616878363ffffffff6126aa16565b979650505050505050565b60006123bb82856001600160a01b031663dd62ed3e30876040518363ffffffff1660e01b815260040161235f929190613594565b60206040518083038186803b15801561237757600080fd5b505afa15801561238b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123af919061331a565b9063ffffffff61296116565b9050611cd88463095ea7b360e01b85846040516024016120a092919061365c565b600081600001518487806001600160a01b0316826001600160a01b03161461253a57816001600160a01b03168360008151811061241557fe5b60200260200101516001600160a01b0316148061247d57506007546001600160a01b03838116911614801561247d5750600080516020613eaf8339815191526001600160a01b03168360008151811061246a57fe5b60200260200101516001600160a01b0316145b6124995760405162461bcd60e51b81526004016103bf906138a1565b806001600160a01b0316836001855103815181106124b357fe5b60200260200101516001600160a01b0316148061251e57506007546001600160a01b03828116911614801561251e5750600080516020613eaf8339815191526001600160a01b03168360018551038151811061250b57fe5b60200260200101516001600160a01b0316145b61253a5760405162461bcd60e51b81526004016103bf90613a75565b866001600160a01b0316896001600160a01b0316141561255c57879350611a01565b6040805160e0810182526001546001600160a01b03908116825260025481166020830152600354811682840152600480548216606084015260055482166080840152600654821660a084015260075490911660c08301529151636fd7d45f60e01b8152733026eb2097468fcdc5387af73379948f8aa0a86092636fd7d45f926119ae9290918d918c918c9101613c2c565b8047101561260d5760405162461bcd60e51b81526004016103bf906138ea565b6000826001600160a01b03168260405161262690613591565b60006040518083038185875af1925050503d8060008114612663576040519150601f19603f3d011682016040523d82523d6000602084013e612668565b606091505b50509050806117ba5760405162461bcd60e51b81526004016103bf90613844565b81156117ba576117ba6001600160a01b03841682308563ffffffff61207f16565b6117ba8363a9059cbb60e01b84846040516024016120a092919061365c565b606061271e826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166129869092919063ffffffff16565b8051909150156117ba578080602001905181019061273c9190613002565b6117ba5760405162461bcd60e51b81526004016103bf90613af6565b600081600001518685806001600160a01b0316826001600160a01b0316146128b657816001600160a01b03168360008151811061279157fe5b60200260200101516001600160a01b031614806127f957506007546001600160a01b0383811691161480156127f95750600080516020613eaf8339815191526001600160a01b0316836000815181106127e657fe5b60200260200101516001600160a01b0316145b6128155760405162461bcd60e51b81526004016103bf906138a1565b806001600160a01b03168360018551038151811061282f57fe5b60200260200101516001600160a01b0316148061289a57506007546001600160a01b03828116911614801561289a5750600080516020613eaf8339815191526001600160a01b03168360018551038151811061288757fe5b60200260200101516001600160a01b0316145b6128b65760405162461bcd60e51b81526004016103bf90613a75565b6040805160e0810182526001546001600160a01b03908116825260025481166020830152600354811682840152600480548216606084015260055482166080840152600654821660a084015260075490911660c08301529151630355f53b60e31b8152733026eb2097468fcdc5387af73379948f8aa0a86092631aafa9d8926119ae9290918d918c918c9101613c2c565b6117ba6001600160a01b038416828463ffffffff6126aa16565b6000828201838110156111ff5760405162461bcd60e51b81526004016103bf906137d6565b60606106e884846000858561299a85612a25565b6129b65760405162461bcd60e51b81526004016103bf90613abf565b60006060866001600160a01b031685876040516129d39190613575565b60006040518083038185875af1925050503d8060008114612a10576040519150601f19603f3d011682016040523d82523d6000602084013e612a15565b606091505b5091509150612320828286612a2b565b3b151590565b60608315612a3a5750816106eb565b825115612a4a5782518084602001fd5b8160405162461bcd60e51b81526004016103bf91906136e4565b60405180610120016040528060006001600160a01b031681526020016000815260200160006001600160a01b0316815260200160001515815260200160006001600160a01b0316815260200160008152602001612abf612ade565b8152602001612acc612b0c565b8152602001612ad9612b0c565b905290565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b6040518060800160405280606081526020016060815260200160006001600160a01b0316815260200160006004811115612ad957fe5b803561120281613e8b565b805161120281613e8b565b600082601f830112612b68578081fd5b8135612b7b612b7682613e33565b613e0c565b818152915060208083019084810181840286018201871015612b9c57600080fd5b6000805b85811015612bcc57823562ffffff81168114612bba578283fd5b85529383019391830191600101612ba0565b50505050505092915050565b600082601f830112612be8578081fd5b8151612bf6612b7682613e33565b818152915060208083019084810181840286018201871015612c1757600080fd5b60005b84811015612c3657815184529282019290820190600101612c1a565b505050505092915050565b803561120281613ea0565b80356005811061120257600080fd5b600060a08284031215612c6c578081fd5b612c7660a0613e0c565b90508135612c8381613e8b565b81526020820135612c9381613e8b565b6020820152604082810135908201526060820135612cb081613e8b565b806060830152506080820135608082015292915050565b600060808284031215612cd8578081fd5b612ce26080613e0c565b9050813567ffffffffffffffff80821115612cfc57600080fd5b81840185601f820112612d0e57600080fd5b80359250612d1e612b7684613e33565b808482526020808301925080840189828389028701011115612d3f57600080fd5b600094505b86851015612d6a57612d568a82612b42565b845260019490940193928101928101612d44565b5081875280880135955084861115612d8157600080fd5b612d8d89878a01612b58565b81880152505050505050612da48360408401612b42565b6040820152612db68360608401612c4c565b606082015292915050565b600060208284031215612dd2578081fd5b81516111ff81613e8b565b60008060008060008060a08789031215612df5578182fd5b8635612e0081613e8b565b955060208701359450604087013593506060870135612e1e81613e8b565b9250608087013567ffffffffffffffff80821115612e3a578384fd5b8189018a601f820112612e4b578485fd5b8035925081831115612e5b578485fd5b8a6020848301011115612e6c578485fd5b979a9699509497509295602001949293505050565b600080600060608486031215612e95578081fd5b835167ffffffffffffffff80821115612eac578283fd5b81860187601f820112612ebd578384fd5b80519250612ecd612b7684613e33565b80848252602080830192508084018b828389028701011115612eed578788fd5b8794505b86851015612f1757612f038c82612b4d565b845260019490940193928101928101612ef1565b508901519097509350505080821115612f2e578283fd5b612f3a87838801612bd8565b93506040860151915080821115612f4f578283fd5b50612f5c86828701612bd8565b9150509250925092565b60006020808385031215612f78578182fd5b823567ffffffffffffffff811115612f8e578283fd5b80840185601f820112612f9f578384fd5b80359150612faf612b7683613e33565b8281528381019082850185850284018601891015612fcb578687fd5b8693505b84841015612ff6578035612fe281613e8b565b835260019390930192918501918501612fcf565b50979650505050505050565b600060208284031215613013578081fd5b81516111ff81613ea0565b60006020828403121561302f578081fd5b81356111ff81613e8b565b60008060008060008060c08789031215613052578384fd5b863561305d81613e8b565b955060208701359450604087013561307481613e8b565b935060608701359250608087013567ffffffffffffffff80821115613097578384fd5b6130a38a838b01612cc7565b935060a08901359150808211156130b8578283fd5b506130c589828a01612cc7565b9150509295509295509295565b6000806000606084860312156130e6578081fd5b83356130f181613e8b565b925060208401359150604084013561310881613ea0565b809150509250925092565b60008060008060808587031215613128578182fd5b843561313381613e8b565b935060208501359250604085013567ffffffffffffffff80821115613156578384fd5b61316288838901612cc7565b93506060870135915080821115613177578283fd5b5061318487828801612cc7565b91505092959194509250565b600080600080600060a086880312156131a7578283fd5b85356131b281613e8b565b94506020860135935060408601359250606086013567ffffffffffffffff808211156131dc578283fd5b6131e889838a01612cc7565b935060808801359150808211156131fd578283fd5b5061320a88828901612cc7565b9150509295509295909350565b600060208284031215613228578081fd5b813567ffffffffffffffff8082111561323f578283fd5b8184016101a08187031215613252578384fd5b61325d610120613e0c565b92506132698682612b42565b8352602081013560208401526132828660408301612b42565b60408401526132948660608301612c41565b60608401526132a68660808301612b42565b608084015260a081013560a08401526132c28660c08301612c5b565b60c0840152610160810135828111156132d9578485fd5b6132e587828401612cc7565b60e085015250610180810135828111156132fd578485fd5b61330987828401612cc7565b610100850152509195945050505050565b60006020828403121561332b578081fd5b5051919050565b6001600160a01b0316815260200190565b62ffffff16815260200190565b6001600160a01b03169052565b15159052565b6000815180845261337b816020860160208601613e5f565b601f01601f19169290920160200192915050565b6005811061339957fe5b9052565b80516001600160a01b03908116835260208083015182169084015260408083015182169084015260608083015182169084015260808083015182169084015260a08281015182169084015260c09182015116910152565b80516001600160a01b0390811683526020808301518216908401526040808301519084015260608083015190911690830152608090810151910152565b805160808084528151908401819052600091602091839160a08701919084015b818410156134785780516001600160a01b0316835260019390930192918401918401613451565b505084830151868203878501528051808352859350908401918401905b808410156134ba57825162ffffff168252600193909301929184019190840190613495565b50604086015193506134cf6040880185613350565b60608601519350610465606088018561338f565b6000608083018251608085528181516134fc8185613df5565b60209450859390925084015b8184101561352b5761351b838251613332565b6001949094019392508401613508565b5050828501519150858103838701528082516135478184613df5565b86948601935091505b808410156134ba57613563828451613343565b91508483019250600184019350613550565b60008251613587818460208701613e5f565b9190910192915050565b90565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b038681168252851660208201526040810184905260a0606082018190526000906135e190830185613363565b905061ffff831660808301529695505050505050565b6001600160a01b03978816815295871660208701529386166040860152918516606085015284166080840152831660a083015290911660c082015260e00190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b0393841681526020810192909252909116604082015260600190565b6001600160a01b03948516815260208101939093529216604082015261ffff909116606082015260800190565b901515815260200190565b6001600160a01b0391909116815260200190565b6000602082526111ff6020830184613363565b6020808252602c908201527f45786368616e676549737375616e63653a20494e53554646494349454e54204f60408201526b155514155508105353d5539560a21b606082015260800190565b60208082526028908201527f466c6173686c6f616e206e6f7420696e6974696174656420627920746869732060408201526718dbdb9d1c9858dd60c21b606082015260800190565b6020808252602b908201527f45786368616e676549737375616e63653a20494e53554646494349454e54204960408201526a1394155508105353d5539560aa1b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b6020808252601e908201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604082015260600190565b6020808252603a908201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260408201527f6563697069656e74206d61792068617665207265766572746564000000000000606082015260800190565b60208082526029908201527f45786368616e676549737375616e63653a20494e5055545f544f4b454e5f4e4f6040820152680a8be929cbea082a8960bb1b606082015260800190565b6020808252601d908201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604082015260600190565b6020808252602b908201527f45786368616e676549737375616e63653a20544f4f204d414e5920455155495460408201526a5920504f534954494f4e5360a81b606082015260800190565b6020808252601a908201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604082015260600190565b60208082526025908201527f45786368616e676549737375616e63653a20544f4f204d414e5920434f4d504f6040820152644e454e545360d81b606082015260800190565b6020808252602c908201527f45786368616e676549737375616e63653a204f5645525350454e5420434f4c4c60408201526b20aa22a920a6102a27a5a2a760a11b606082015260800190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252602a908201527f45786368616e676549737375616e63653a204f55545055545f544f4b454e5f4e60408201526909ea8be929cbea082a8960b31b606082015260800190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60208082526029908201527f45786368616e676549737375616e63653a20544f4f204d414e59204445425420604082015268504f534954494f4e5360b81b606082015260800190565b60208082526017908201527f466c6173686c6f616e20616c72656164792074616b656e000000000000000000604082015260600190565b6020808252818101527f45786368616e676549737375616e63653a204161766520506f6f6c204f4e4c59604082015260600190565b6000610140613c3b838861339d565b8560e08401528461010084015280610120840152612320818401856134e3565b6000610120613c7383613c6e8854613e53565b613350565b613c806001870154613e53565b613c8d6020850182613350565b50613c9b6002870154613e53565b613ca86040850182613350565b50613cb66003870154613e53565b613cc36060850182613350565b50613cd16004870154613e53565b613cde6080850182613350565b50613cec6005870154613e53565b613cf960a0850182613350565b50613d076006870154613e53565b613d1460c0850182613350565b508060e0840152613d27818401866134e3565b91505082610100830152949350505050565b600060208252613d4d602083018451613350565b602083015160408301526040830151613d696060840182613350565b506060830151613d7c608084018261335d565b506080830151613d8f60a0840182613350565b5060a083015160c083015260c0830151613dac60e08401826133f4565b5060e08301516101a080610180850152613dca6101c0850183613431565b610100860151858203601f19018387015292506104658184613431565b60a0810161120282846133f4565b90815260200190565b918252602082015260400190565b60405181810167ffffffffffffffff81118282101715613e2b57600080fd5b604052919050565b600067ffffffffffffffff821115613e49578081fd5b5060209081020190565b6001600160a01b031690565b60005b83811015613e7a578181015183820152602001613e62565b83811115611cd85750506000910152565b6001600160a01b038116811461071f57600080fd5b801515811461071f57600080fdfe000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeea26469706673582212207ec564389bb528b8c3b71e90b21364e629cb9438a4b83b00086359ff8f4932dc64736f6c634300060a0033
Verified Source Code Partial Match
Compiler: v0.6.10+commit.00c0fcaf
EVM: istanbul
Optimization: Yes (200 runs)
KeeperCompatibleInterface.sol 51 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface KeeperCompatibleInterface {
/**
* @notice method that is simulated by the keepers to see if any work actually
* needs to be performed. This method does does not actually need to be
* executable, and since it is only ever simulated it can consume lots of gas.
* @dev To ensure that it is never called, you may want to add the
* cannotExecute modifier from KeeperBase to your implementation of this
* method.
* @param checkData specified in the upkeep registration so it is always the
* same for a registered upkeep. This can easily be broken down into specific
* arguments using `abi.decode`, so multiple upkeeps can be registered on the
* same contract and easily differentiated by the contract.
* @return upkeepNeeded boolean to indicate whether the keeper should call
* performUpkeep or not.
* @return performData bytes that the keeper should call performUpkeep with, if
* upkeep is needed. If you would like to encode data to decode later, try
* `abi.encode`.
*/
function checkUpkeep(
bytes calldata checkData
)
external
returns (
bool upkeepNeeded,
bytes memory performData
);
/**
* @notice method that is actually executed by the keepers, via the registry.
* The data returned by the checkUpkeep simulation will be passed into
* this method to actually be executed.
* @dev The input to this method should not be trusted, and the caller of the
* method should not even be restricted to any single registry. Anyone should
* be able call it, and the input should be validated, there is no guarantee
* that the data passed in is the performData returned from checkUpkeep. This
* could happen due to malicious keepers, racing keepers, or simply a state
* change while the performUpkeep transaction is waiting for confirmation.
* Always validate the data passed in.
* @param performData is the data which was passed back from the checkData
* simulation. If it is encoded, it can easily be decoded into other types by
* calling `abi.decode`. This data should not be trusted, and should be
* validated against the contract's current state.
*/
function performUpkeep(
bytes calldata performData
) external;
}
KeeperBase.sol 21 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract KeeperBase {
/**
* @notice method that allows it to be simulated via eth_call by checking that
* the sender is the zero address.
*/
function preventExecution() internal view {
require(tx.origin == address(0), "only for simulated backend");
}
/**
* @notice modifier that allows it to be simulated via eth_call by checking
* that the sender is the zero address.
*/
modifier cannotExecute() {
preventExecution();
_;
}
}
KeeperCompatible.sol 7 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "./KeeperBase.sol";
import "./interfaces/KeeperCompatibleInterface.sol";
abstract contract KeeperCompatible is KeeperBase, KeeperCompatibleInterface {}
Ownable.sol 68 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
MerkleProof.sol 33 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev These functions deal with verification of Merkle trees (hash trees),
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
// Hash(current element of the proof + current computed hash)
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
// Check if the computed hash (root) is equal to the provided root
return computedHash == root;
}
}
Context.sol 5 lines
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; import "../utils/Context.sol";
Math.sol 31 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow, so we distribute
return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
}
}
SafeMath.sol 214 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a / b);
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a % b);
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: modulo by zero");
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
return a - b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryDiv}.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a % b;
}
}
SignedSafeMath.sol 92 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @title SignedSafeMath
* @dev Signed math operations with safety checks that revert on error.
*/
library SignedSafeMath {
int256 constant private _INT256_MIN = -2**255;
/**
* @dev Returns the multiplication of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(int256 a, int256 b) internal pure returns (int256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
require(!(a == -1 && b == _INT256_MIN), "SignedSafeMath: multiplication overflow");
int256 c = a * b;
require(c / a == b, "SignedSafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two signed integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(int256 a, int256 b) internal pure returns (int256) {
require(b != 0, "SignedSafeMath: division by zero");
require(!(b == -1 && a == _INT256_MIN), "SignedSafeMath: division overflow");
int256 c = a / b;
return c;
}
/**
* @dev Returns the subtraction of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(int256 a, int256 b) internal pure returns (int256) {
int256 c = a - b;
require((b >= 0 && c <= a) || (b < 0 && c > a), "SignedSafeMath: subtraction overflow");
return c;
}
/**
* @dev Returns the addition of two signed integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(int256 a, int256 b) internal pure returns (int256) {
int256 c = a + b;
require((b >= 0 && c >= a) || (b < 0 && c < a), "SignedSafeMath: addition overflow");
return c;
}
}
ERC20.sol 306 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../../utils/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20 {
using SafeMath for uint256;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for {name} and {symbol}, initializes {decimals} with
* a default value of 18.
*
* To select a different value for {decimals}, use {_setupDecimals}.
*
* All three of these values are immutable: they can only be set once during
* construction.
*/
constructor (string memory name_, string memory symbol_) public {
_name = name_;
_symbol = symbol_;
_decimals = 18;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
* called.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return _decimals;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* Requirements:
*
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Sets {decimals} to a value other than the default one of 18.
*
* WARNING: This function should only be called from the constructor. Most
* applications that interact with token contracts will not expect
* {decimals} to ever change, and may work incorrectly if it does.
*/
function _setupDecimals(uint8 decimals_) internal virtual {
_decimals = decimals_;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}
IERC20.sol 77 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @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);
}
SafeERC20.sol 75 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "./IERC20.sol";
import "../../math/SafeMath.sol";
import "../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @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, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
IERC721Receiver.sol 21 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
*/
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}
Address.sol 189 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @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, it is bubbled up by this
* function (like regular Solidity function calls).
*
* 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.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @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`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
// 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
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
Context.sol 24 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
ReentrancyGuard.sol 62 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor () internal {
_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 make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
SafeCast.sol 211 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128) {
require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\'t fit in 128 bits");
return int128(value);
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64) {
require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\'t fit in 64 bits");
return int64(value);
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32) {
require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\'t fit in 32 bits");
return int32(value);
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16) {
require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\'t fit in 16 bits");
return int16(value);
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8) {
require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\'t fit in 8 bits");
return int8(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
require(value < 2**255, "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}
IUniswapV2Factory.sol 17 lines
pragma solidity >=0.5.0;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
IUniswapV2Pair.sol 52 lines
pragma solidity >=0.5.0;
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
IUniswapV2Router01.sol 95 lines
pragma solidity >=0.6.2;
interface IUniswapV2Router01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}
IUniswapV2Router02.sol 44 lines
pragma solidity >=0.6.2;
import './IUniswapV2Router01.sol';
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
AaveLeverageStrategyExtension.sol 1276 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IChainlinkAggregatorV3 } from "../interfaces/IChainlinkAggregatorV3.sol";
import { ILeverageModule } from "../interfaces/ILeverageModule.sol";
import { IProtocolDataProvider } from "../interfaces/IProtocolDataProvider.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { StringArrayUtils } from "../lib/StringArrayUtils.sol";
/**
* @title AaveLeverageStrategyExtension
* @author Set Protocol
*
* Smart contract that enables trustless leverage tokens. This extension is paired with the AaveLeverageModule from Set protocol where module
* interactions are invoked via the IBaseManager contract. Any leveraged token can be constructed as long as the collateral and borrow asset
* is available on Aave. This extension contract also allows the operator to set an ETH reward to incentivize keepers calling the rebalance
* function at different leverage thresholds.
*
*/
contract AaveLeverageStrategyExtension is BaseExtension {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using SafeCast for int256;
using StringArrayUtils for string[];
/* ============ Enums ============ */
enum ShouldRebalance {
NONE, // Indicates no rebalance action can be taken
REBALANCE, // Indicates rebalance() function can be successfully called
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
RIPCORD // Indicates ripcord() function can be successfully called
}
/* ============ Structs ============ */
struct ActionInfo {
uint256 collateralBalance; // Balance of underlying held in Aave in base units (e.g. USDC 10e6)
uint256 borrowBalance; // Balance of underlying borrowed from Aave in base units
uint256 collateralValue; // Valuation in USD adjusted for decimals in precise units (10e18)
uint256 borrowValue; // Valuation in USD adjusted for decimals in precise units (10e18)
uint256 collateralPrice; // Price of collateral in precise units (10e18) from Chainlink
uint256 borrowPrice; // Price of borrow asset in precise units (10e18) from Chainlink
uint256 setTotalSupply; // Total supply of SetToken
}
struct LeverageInfo {
ActionInfo action;
uint256 currentLeverageRatio; // Current leverage ratio of Set
uint256 slippageTolerance; // Allowable percent trade slippage in preciseUnits (1% = 10^16)
uint256 twapMaxTradeSize; // Max trade size in collateral units allowed for rebalance action
string exchangeName; // Exchange to use for trade
}
struct ContractSettings {
ISetToken setToken; // Instance of leverage token
ILeverageModule leverageModule; // Instance of Aave leverage module
IProtocolDataProvider aaveProtocolDataProvider; // Instance of Aave protocol data provider
IChainlinkAggregatorV3 collateralPriceOracle; // Chainlink oracle feed that returns prices in 8 decimals for collateral asset
IChainlinkAggregatorV3 borrowPriceOracle; // Chainlink oracle feed that returns prices in 8 decimals for borrow asset
IERC20 targetCollateralAToken; // Instance of target collateral aToken asset
IERC20 targetBorrowDebtToken; // Instance of target borrow variable debt token asset
address collateralAsset; // Address of underlying collateral
address borrowAsset; // Address of underlying borrow asset
uint256 collateralDecimalAdjustment; // Decimal adjustment for chainlink oracle of the collateral asset. Equal to 28-collateralDecimals (10^18 * 10^18 / 10^decimals / 10^8)
uint256 borrowDecimalAdjustment; // Decimal adjustment for chainlink oracle of the borrowing asset. Equal to 28-borrowDecimals (10^18 * 10^18 / 10^decimals / 10^8)
}
struct MethodologySettings {
uint256 targetLeverageRatio; // Long term target ratio in precise units (10e18)
uint256 minLeverageRatio; // In precise units (10e18). If current leverage is below, rebalance target is this ratio
uint256 maxLeverageRatio; // In precise units (10e18). If current leverage is above, rebalance target is this ratio
uint256 recenteringSpeed; // % at which to rebalance back to target leverage in precise units (10e18)
uint256 rebalanceInterval; // Period of time required since last rebalance timestamp in seconds
}
struct ExecutionSettings {
uint256 unutilizedLeveragePercentage; // Percent of max borrow left unutilized in precise units (1% = 10e16)
uint256 slippageTolerance; // % in precise units to price min token receive amount from trade quantities
uint256 twapCooldownPeriod; // Cooldown period required since last trade timestamp in seconds
}
struct ExchangeSettings {
uint256 twapMaxTradeSize; // Max trade size in collateral base units
uint256 exchangeLastTradeTimestamp; // Timestamp of last trade made with this exchange
uint256 incentivizedTwapMaxTradeSize; // Max trade size for incentivized rebalances in collateral base units
bytes leverExchangeData; // Arbitrary exchange data passed into rebalance function for levering up
bytes deleverExchangeData; // Arbitrary exchange data passed into rebalance function for delevering
}
struct IncentiveSettings {
uint256 etherReward; // ETH reward for incentivized rebalances
uint256 incentivizedLeverageRatio; // Leverage ratio for incentivized rebalances
uint256 incentivizedSlippageTolerance; // Slippage tolerance percentage for incentivized rebalances
uint256 incentivizedTwapCooldownPeriod; // TWAP cooldown in seconds for incentivized rebalances
}
/* ============ Events ============ */
event Engaged(uint256 _currentLeverageRatio, uint256 _newLeverageRatio, uint256 _chunkRebalanceNotional, uint256 _totalRebalanceNotional);
event Rebalanced(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
);
event RebalanceIterated(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
);
event RipcordCalled(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _rebalanceNotional,
uint256 _etherIncentive
);
event Disengaged(uint256 _currentLeverageRatio, uint256 _newLeverageRatio, uint256 _chunkRebalanceNotional, uint256 _totalRebalanceNotional);
event MethodologySettingsUpdated(
uint256 _targetLeverageRatio,
uint256 _minLeverageRatio,
uint256 _maxLeverageRatio,
uint256 _recenteringSpeed,
uint256 _rebalanceInterval
);
event ExecutionSettingsUpdated(
uint256 _unutilizedLeveragePercentage,
uint256 _twapCooldownPeriod,
uint256 _slippageTolerance
);
event IncentiveSettingsUpdated(
uint256 _etherReward,
uint256 _incentivizedLeverageRatio,
uint256 _incentivizedSlippageTolerance,
uint256 _incentivizedTwapCooldownPeriod
);
event ExchangeUpdated(
string _exchangeName,
uint256 twapMaxTradeSize,
uint256 exchangeLastTradeTimestamp,
uint256 incentivizedTwapMaxTradeSize,
bytes leverExchangeData,
bytes deleverExchangeData
);
event ExchangeAdded(
string _exchangeName,
uint256 twapMaxTradeSize,
uint256 exchangeLastTradeTimestamp,
uint256 incentivizedTwapMaxTradeSize,
bytes leverExchangeData,
bytes deleverExchangeData
);
event ExchangeRemoved(
string _exchangeName
);
/* ============ Modifiers ============ */
/**
* Throws if rebalance is currently in TWAP`
*/
modifier noRebalanceInProgress() virtual {
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
_;
}
/* ============ State Variables ============ */
ContractSettings internal strategy; // Struct of contracts used in the strategy (SetToken, price oracles, leverage module etc)
MethodologySettings internal methodology; // Struct containing methodology parameters
ExecutionSettings internal execution; // Struct containing execution parameters
mapping(string => ExchangeSettings) internal exchangeSettings; // Mapping from exchange name to exchange settings
IncentiveSettings internal incentive; // Struct containing incentive parameters for ripcord
string[] public enabledExchanges; // Array containing enabled exchanges
uint256 public twapLeverageRatio; // Stored leverage ratio to keep track of target between TWAP rebalances
uint256 public globalLastTradeTimestamp; // Last rebalance timestamp. Current timestamp must be greater than this variable + rebalance interval to rebalance
/* ============ Constructor ============ */
/**
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
*
* @param _manager Address of IBaseManager contract
* @param _strategy Struct of contract addresses
* @param _methodology Struct containing methodology parameters
* @param _execution Struct containing execution parameters
* @param _incentive Struct containing incentive parameters for ripcord
* @param _exchangeNames List of initial exchange names
* @param _exchangeSettings List of structs containing exchange parameters for the initial exchanges
*/
constructor(
IBaseManager _manager,
ContractSettings memory _strategy,
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive,
string[] memory _exchangeNames,
ExchangeSettings[] memory _exchangeSettings
)
public
BaseExtension(_manager)
{
strategy = _strategy;
methodology = _methodology;
execution = _execution;
incentive = _incentive;
for (uint256 i = 0; i < _exchangeNames.length; i++) {
_validateExchangeSettings(_exchangeSettings[i]);
exchangeSettings[_exchangeNames[i]] = _exchangeSettings[i];
enabledExchanges.push(_exchangeNames[i]);
}
_validateNonExchangeSettings(methodology, execution, incentive);
}
/* ============ External Functions ============ */
/**
* OPERATOR ONLY: Engage to target leverage ratio for the first time. SetToken will borrow debt position from Aave and trade for collateral asset. If target
* leverage ratio is above max borrow or max trade size, then TWAP is kicked off. To complete engage if TWAP, any valid caller must call iterateRebalance until target
* is met.
*
* @param _exchangeName the exchange used for trading
*/
function engage(string memory _exchangeName) external onlyOperator {
ActionInfo memory engageInfo = _createActionInfo();
require(engageInfo.setTotalSupply > 0, "SetToken must have > 0 supply");
require(engageInfo.collateralBalance > 0, "Collateral balance must be > 0");
require(engageInfo.borrowBalance == 0, "Debt must be 0");
LeverageInfo memory leverageInfo = LeverageInfo({
action: engageInfo,
currentLeverageRatio: PreciseUnitMath.preciseUnit(), // 1x leverage in precise units
slippageTolerance: execution.slippageTolerance,
twapMaxTradeSize: exchangeSettings[_exchangeName].twapMaxTradeSize,
exchangeName: _exchangeName
});
// Calculate total rebalance units and kick off TWAP if above max borrow or max trade size
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _calculateChunkRebalanceNotional(leverageInfo, methodology.targetLeverageRatio, true);
_lever(leverageInfo, chunkRebalanceNotional);
_updateRebalanceState(
chunkRebalanceNotional,
totalRebalanceNotional,
methodology.targetLeverageRatio,
_exchangeName
);
emit Engaged(
leverageInfo.currentLeverageRatio,
methodology.targetLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA AND ALLOWED CALLER: Rebalance product. If current leverage ratio is between the max and min bounds, then rebalance
* can only be called once the rebalance interval has elapsed since last timestamp. If outside the max and min, rebalance can be called anytime to bring leverage
* ratio back to the max or min bounds. The methodology will determine whether to delever or lever.
*
* Note: If the calculated current leverage ratio is above the incentivized leverage ratio or in TWAP then rebalance cannot be called. Instead, you must call
* ripcord() which is incentivized with a reward in Ether or iterateRebalance().
*
* @param _exchangeName the exchange used for trading
*/
function rebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
// use globalLastTradeTimestamps to prevent multiple rebalances being called with different exchanges during the epoch rebalance
_validateNormalRebalance(leverageInfo, methodology.rebalanceInterval, globalLastTradeTimestamp);
_validateNonTWAP();
uint256 newLeverageRatio = _calculateNewLeverageRatio(leverageInfo.currentLeverageRatio);
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _handleRebalance(leverageInfo, newLeverageRatio);
_updateRebalanceState(chunkRebalanceNotional, totalRebalanceNotional, newLeverageRatio, _exchangeName);
emit Rebalanced(
leverageInfo.currentLeverageRatio,
newLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA AND ALLOWED CALLER: Iterate a rebalance when in TWAP. TWAP cooldown period must have elapsed. If price moves advantageously, then exit without rebalancing
* and clear TWAP state. This function can only be called when below incentivized leverage ratio and in TWAP state.
*
* @param _exchangeName the exchange used for trading
*/
function iterateRebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
// Use the exchangeLastTradeTimestamp since cooldown periods are measured on a per-exchange basis, allowing it to rebalance multiple time in quick
// succession with different exchanges
_validateNormalRebalance(leverageInfo, execution.twapCooldownPeriod, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
_validateTWAP();
uint256 chunkRebalanceNotional;
uint256 totalRebalanceNotional;
if (!_isAdvantageousTWAP(leverageInfo.currentLeverageRatio)) {
(chunkRebalanceNotional, totalRebalanceNotional) = _handleRebalance(leverageInfo, twapLeverageRatio);
}
// If not advantageous, then rebalance is skipped and chunk and total rebalance notional are both 0, which means TWAP state is
// cleared
_updateIterateState(chunkRebalanceNotional, totalRebalanceNotional, _exchangeName);
emit RebalanceIterated(
leverageInfo.currentLeverageRatio,
twapLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA: In case the current leverage ratio exceeds the incentivized leverage threshold, the ripcord function can be called by anyone to return leverage ratio
* back to the max leverage ratio. This function typically would only be called during times of high downside volatility and / or normal keeper malfunctions. The caller
* of ripcord() will receive a reward in Ether. The ripcord function uses it's own TWAP cooldown period, slippage tolerance and TWAP max trade size which are typically
* looser than in regular rebalances.
*
* @param _exchangeName the exchange used for trading
*/
function ripcord(string memory _exchangeName) external onlyEOA {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
incentive.incentivizedSlippageTolerance,
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize,
_exchangeName
);
// Use the exchangeLastTradeTimestamp so it can ripcord quickly with multiple exchanges
_validateRipcord(leverageInfo, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
( uint256 chunkRebalanceNotional, ) = _calculateChunkRebalanceNotional(leverageInfo, methodology.maxLeverageRatio, false);
_delever(leverageInfo, chunkRebalanceNotional);
_updateRipcordState(_exchangeName);
uint256 etherTransferred = _transferEtherRewardToCaller(incentive.etherReward);
emit RipcordCalled(
leverageInfo.currentLeverageRatio,
methodology.maxLeverageRatio,
chunkRebalanceNotional,
etherTransferred
);
}
/**
* OPERATOR ONLY: Return leverage ratio to 1x and delever to repay loan. This can be used for upgrading or shutting down the strategy. SetToken will redeem
* collateral position and trade for debt position to repay Aave. If the chunk rebalance size is less than the total notional size, then this function will
* delever and repay entire borrow balance on Aave. If chunk rebalance size is above max borrow or max trade size, then operator must
* continue to call this function to complete repayment of loan. The function iterateRebalance will not work.
*
* Note: Delever to 0 will likely result in additional units of the borrow asset added as equity on the SetToken due to oracle price / market price mismatch
*
* @param _exchangeName the exchange used for trading
*/
function disengage(string memory _exchangeName) external onlyOperator {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
uint256 newLeverageRatio = PreciseUnitMath.preciseUnit();
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _calculateChunkRebalanceNotional(leverageInfo, newLeverageRatio, false);
if (totalRebalanceNotional > chunkRebalanceNotional) {
_delever(leverageInfo, chunkRebalanceNotional);
} else {
_deleverToZeroBorrowBalance(leverageInfo, totalRebalanceNotional);
}
emit Disengaged(
leverageInfo.currentLeverageRatio,
newLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* OPERATOR ONLY: Set methodology settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newMethodologySettings Struct containing methodology parameters
*/
function setMethodologySettings(MethodologySettings memory _newMethodologySettings) external onlyOperator noRebalanceInProgress {
methodology = _newMethodologySettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit MethodologySettingsUpdated(
methodology.targetLeverageRatio,
methodology.minLeverageRatio,
methodology.maxLeverageRatio,
methodology.recenteringSpeed,
methodology.rebalanceInterval
);
}
/**
* OPERATOR ONLY: Set execution settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newExecutionSettings Struct containing execution parameters
*/
function setExecutionSettings(ExecutionSettings memory _newExecutionSettings) external onlyOperator noRebalanceInProgress {
execution = _newExecutionSettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit ExecutionSettingsUpdated(
execution.unutilizedLeveragePercentage,
execution.twapCooldownPeriod,
execution.slippageTolerance
);
}
/**
* OPERATOR ONLY: Set incentive settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newIncentiveSettings Struct containing incentive parameters
*/
function setIncentiveSettings(IncentiveSettings memory _newIncentiveSettings) external onlyOperator noRebalanceInProgress {
incentive = _newIncentiveSettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit IncentiveSettingsUpdated(
incentive.etherReward,
incentive.incentivizedLeverageRatio,
incentive.incentivizedSlippageTolerance,
incentive.incentivizedTwapCooldownPeriod
);
}
/**
* OPERATOR ONLY: Add a new enabled exchange for trading during rebalances. New exchanges will have their exchangeLastTradeTimestamp set to 0. Adding
* exchanges during rebalances is allowed, as it is not possible to enter an unexpected state while doing so.
*
* @param _exchangeName Name of the exchange
* @param _exchangeSettings Struct containing exchange parameters
*/
function addEnabledExchange(
string memory _exchangeName,
ExchangeSettings memory _exchangeSettings
)
external
onlyOperator
{
require(exchangeSettings[_exchangeName].twapMaxTradeSize == 0, "Exchange already enabled");
_validateExchangeSettings(_exchangeSettings);
exchangeSettings[_exchangeName].twapMaxTradeSize = _exchangeSettings.twapMaxTradeSize;
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize = _exchangeSettings.incentivizedTwapMaxTradeSize;
exchangeSettings[_exchangeName].leverExchangeData = _exchangeSettings.leverExchangeData;
exchangeSettings[_exchangeName].deleverExchangeData = _exchangeSettings.deleverExchangeData;
exchangeSettings[_exchangeName].exchangeLastTradeTimestamp = 0;
enabledExchanges.push(_exchangeName);
emit ExchangeAdded(
_exchangeName,
_exchangeSettings.twapMaxTradeSize,
_exchangeSettings.exchangeLastTradeTimestamp,
_exchangeSettings.incentivizedTwapMaxTradeSize,
_exchangeSettings.leverExchangeData,
_exchangeSettings.deleverExchangeData
);
}
/**
* OPERATOR ONLY: Removes an exchange. Reverts if the exchange is not already enabled. Removing exchanges during rebalances is allowed,
* as it is not possible to enter an unexpected state while doing so.
*
* @param _exchangeName Name of exchange to remove
*/
function removeEnabledExchange(string memory _exchangeName) external onlyOperator {
require(exchangeSettings[_exchangeName].twapMaxTradeSize != 0, "Exchange not enabled");
delete exchangeSettings[_exchangeName];
enabledExchanges.removeStorage(_exchangeName);
emit ExchangeRemoved(_exchangeName);
}
/**
* OPERATOR ONLY: Updates the settings of an exchange. Reverts if exchange is not already added. When updating an exchange, exchangeLastTradeTimestamp
* is preserved. Updating exchanges during rebalances is allowed, as it is not possible to enter an unexpected state while doing so. Note: Need to
* pass in all existing parameters even if only changing a few settings.
*
* @param _exchangeName Name of the exchange
* @param _exchangeSettings Struct containing exchange parameters
*/
function updateEnabledExchange(
string memory _exchangeName,
ExchangeSettings memory _exchangeSettings
)
external
onlyOperator
{
require(exchangeSettings[_exchangeName].twapMaxTradeSize != 0, "Exchange not enabled");
_validateExchangeSettings(_exchangeSettings);
exchangeSettings[_exchangeName].twapMaxTradeSize = _exchangeSettings.twapMaxTradeSize;
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize = _exchangeSettings.incentivizedTwapMaxTradeSize;
exchangeSettings[_exchangeName].leverExchangeData = _exchangeSettings.leverExchangeData;
exchangeSettings[_exchangeName].deleverExchangeData = _exchangeSettings.deleverExchangeData;
emit ExchangeUpdated(
_exchangeName,
_exchangeSettings.twapMaxTradeSize,
_exchangeSettings.exchangeLastTradeTimestamp,
_exchangeSettings.incentivizedTwapMaxTradeSize,
_exchangeSettings.leverExchangeData,
_exchangeSettings.deleverExchangeData
);
}
/**
* OPERATOR ONLY: Withdraw entire balance of ETH in this contract to operator. Rebalance must not be in progress
*/
function withdrawEtherBalance() external onlyOperator noRebalanceInProgress {
msg.sender.transfer(address(this).balance);
}
receive() external payable {}
/* ============ External Getter Functions ============ */
/**
* Get current leverage ratio. Current leverage ratio is defined as the USD value of the collateral divided by the USD value of the SetToken. Prices for collateral
* and borrow asset are retrieved from the Chainlink Price Oracle.
*
* return currentLeverageRatio Current leverage ratio in precise units (10e18)
*/
function getCurrentLeverageRatio() public view returns(uint256) {
ActionInfo memory currentLeverageInfo = _createActionInfo();
return _calculateCurrentLeverageRatio(currentLeverageInfo.collateralValue, currentLeverageInfo.borrowValue);
}
/**
* Calculates the chunk rebalance size. This can be used by external contracts and keeper bots to calculate the optimal exchange to rebalance with.
* Note: this function does not take into account timestamps, so it may return a nonzero value even when shouldRebalance would return ShouldRebalance.NONE for
* all exchanges (since minimum delays have not elapsed)
*
* @param _exchangeNames Array of exchange names to get rebalance sizes for
*
* @return sizes Array of total notional chunk size. Measured in the asset that would be sold
* @return sellAsset Asset that would be sold during a rebalance
* @return buyAsset Asset that would be purchased during a rebalance
*/
function getChunkRebalanceNotional(
string[] calldata _exchangeNames
)
external
view
returns(uint256[] memory sizes, address sellAsset, address buyAsset)
{
uint256 newLeverageRatio;
uint256 currentLeverageRatio = getCurrentLeverageRatio();
bool isRipcord = false;
// if over incentivized leverage ratio, always ripcord
if (currentLeverageRatio > incentive.incentivizedLeverageRatio) {
newLeverageRatio = methodology.maxLeverageRatio;
isRipcord = true;
// if we are in an ongoing twap, use the cached twapLeverageRatio as our target leverage
} else if (twapLeverageRatio > 0) {
newLeverageRatio = twapLeverageRatio;
// if all else is false, then we would just use the normal rebalance new leverage ratio calculation
} else {
newLeverageRatio = _calculateNewLeverageRatio(currentLeverageRatio);
}
ActionInfo memory actionInfo = _createActionInfo();
bool isLever = newLeverageRatio > currentLeverageRatio;
sizes = new uint256[](_exchangeNames.length);
for (uint256 i = 0; i < _exchangeNames.length; i++) {
LeverageInfo memory leverageInfo = LeverageInfo({
action: actionInfo,
currentLeverageRatio: currentLeverageRatio,
slippageTolerance: isRipcord ? incentive.incentivizedSlippageTolerance : execution.slippageTolerance,
twapMaxTradeSize: isRipcord ?
exchangeSettings[_exchangeNames[i]].incentivizedTwapMaxTradeSize :
exchangeSettings[_exchangeNames[i]].twapMaxTradeSize,
exchangeName: _exchangeNames[i]
});
(uint256 collateralNotional, ) = _calculateChunkRebalanceNotional(leverageInfo, newLeverageRatio, isLever);
// _calculateBorrowUnits can convert both unit and notional values
sizes[i] = isLever ? _calculateBorrowUnits(collateralNotional, leverageInfo.action) : collateralNotional;
}
sellAsset = isLever ? strategy.borrowAsset : strategy.collateralAsset;
buyAsset = isLever ? strategy.collateralAsset : strategy.borrowAsset;
}
/**
* Get current Ether incentive for when current leverage ratio exceeds incentivized leverage ratio and ripcord can be called. If ETH balance on the contract is
* below the etherReward, then return the balance of ETH instead.
*
* return etherReward Quantity of ETH reward in base units (10e18)
*/
function getCurrentEtherIncentive() external view returns(uint256) {
uint256 currentLeverageRatio = getCurrentLeverageRatio();
if (currentLeverageRatio >= incentive.incentivizedLeverageRatio) {
// If ETH reward is below the balance on this contract, then return ETH balance on contract instead
return incentive.etherReward < address(this).balance ? incentive.etherReward : address(this).balance;
} else {
return 0;
}
}
/**
* Helper that checks if conditions are met for rebalance or ripcord. Returns an enum with 0 = no rebalance, 1 = call rebalance(), 2 = call iterateRebalance()
* 3 = call ripcord()
*
* @return (string[] memory, ShouldRebalance[] memory) List of exchange names and a list of enums representing whether that exchange should rebalance
*/
function shouldRebalance() external view returns(string[] memory, ShouldRebalance[] memory) {
uint256 currentLeverageRatio = getCurrentLeverageRatio();
return _shouldRebalance(currentLeverageRatio, methodology.minLeverageRatio, methodology.maxLeverageRatio);
}
/**
* Helper that checks if conditions are met for rebalance or ripcord with custom max and min bounds specified by caller. This function simplifies the
* logic for off-chain keeper bots to determine what threshold to call rebalance when leverage exceeds max or drops below min. Returns an enum with
* 0 = no rebalance, 1 = call rebalance(), 2 = call iterateRebalance(), 3 = call ripcord()
*
* @param _customMinLeverageRatio Min leverage ratio passed in by caller
* @param _customMaxLeverageRatio Max leverage ratio passed in by caller
*
* @return (string[] memory, ShouldRebalance[] memory) List of exchange names and a list of enums representing whether that exchange should rebalance
*/
function shouldRebalanceWithBounds(
uint256 _customMinLeverageRatio,
uint256 _customMaxLeverageRatio
)
external
view
returns(string[] memory, ShouldRebalance[] memory)
{
require (
_customMinLeverageRatio <= methodology.minLeverageRatio && _customMaxLeverageRatio >= methodology.maxLeverageRatio,
"Custom bounds must be valid"
);
uint256 currentLeverageRatio = getCurrentLeverageRatio();
return _shouldRebalance(currentLeverageRatio, _customMinLeverageRatio, _customMaxLeverageRatio);
}
/**
* Gets the list of enabled exchanges
*/
function getEnabledExchanges() external view returns (string[] memory) {
return enabledExchanges;
}
/**
* Explicit getter functions for parameter structs are defined as workaround to issues fetching structs that have dynamic types.
*/
function getStrategy() external view returns (ContractSettings memory) { return strategy; }
function getMethodology() external view returns (MethodologySettings memory) { return methodology; }
function getExecution() external view returns (ExecutionSettings memory) { return execution; }
function getIncentive() external view returns (IncentiveSettings memory) { return incentive; }
function getExchangeSettings(string memory _exchangeName) external view returns (ExchangeSettings memory) {
return exchangeSettings[_exchangeName];
}
/* ============ Internal Functions ============ */
/**
* Calculate notional rebalance quantity, whether to chunk rebalance based on max trade size and max borrow and invoke lever on AaveLeverageModule
*
*/
function _lever(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
uint256 collateralRebalanceUnits = _chunkRebalanceNotional.preciseDiv(_leverageInfo.action.setTotalSupply);
uint256 borrowUnits = _calculateBorrowUnits(collateralRebalanceUnits, _leverageInfo.action);
uint256 minReceiveCollateralUnits = _calculateMinCollateralReceiveUnits(collateralRebalanceUnits, _leverageInfo.slippageTolerance);
bytes memory leverCallData = abi.encodeWithSignature(
"lever(address,address,address,uint256,uint256,string,bytes)",
address(strategy.setToken),
strategy.borrowAsset,
strategy.collateralAsset,
borrowUnits,
minReceiveCollateralUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].leverExchangeData
);
invokeManager(address(strategy.leverageModule), leverCallData);
}
/**
* Calculate delever units Invoke delever on AaveLeverageModule.
*/
function _delever(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
uint256 collateralRebalanceUnits = _chunkRebalanceNotional.preciseDiv(_leverageInfo.action.setTotalSupply);
uint256 minRepayUnits = _calculateMinRepayUnits(collateralRebalanceUnits, _leverageInfo.slippageTolerance, _leverageInfo.action);
bytes memory deleverCallData = abi.encodeWithSignature(
"delever(address,address,address,uint256,uint256,string,bytes)",
address(strategy.setToken),
strategy.collateralAsset,
strategy.borrowAsset,
collateralRebalanceUnits,
minRepayUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].deleverExchangeData
);
invokeManager(address(strategy.leverageModule), deleverCallData);
}
/**
* Invoke deleverToZeroBorrowBalance on AaveLeverageModule.
*/
function _deleverToZeroBorrowBalance(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
// Account for slippage tolerance in redeem quantity for the deleverToZeroBorrowBalance function
uint256 maxCollateralRebalanceUnits = _chunkRebalanceNotional
.preciseMul(PreciseUnitMath.preciseUnit().add(execution.slippageTolerance))
.preciseDiv(_leverageInfo.action.setTotalSupply);
bytes memory deleverToZeroBorrowBalanceCallData = abi.encodeWithSignature(
"deleverToZeroBorrowBalance(address,address,address,uint256,string,bytes)",
address(strategy.setToken),
strategy.collateralAsset,
strategy.borrowAsset,
maxCollateralRebalanceUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].deleverExchangeData
);
invokeManager(address(strategy.leverageModule), deleverToZeroBorrowBalanceCallData);
}
/**
* Check whether to delever or lever based on the current vs new leverage ratios. Used in the rebalance() and iterateRebalance() functions
*
* return uint256 Calculated notional to trade
* return uint256 Total notional to rebalance over TWAP
*/
function _handleRebalance(LeverageInfo memory _leverageInfo, uint256 _newLeverageRatio) internal returns(uint256, uint256) {
uint256 chunkRebalanceNotional;
uint256 totalRebalanceNotional;
if (_newLeverageRatio < _leverageInfo.currentLeverageRatio) {
(
chunkRebalanceNotional,
totalRebalanceNotional
) = _calculateChunkRebalanceNotional(_leverageInfo, _newLeverageRatio, false);
_delever(_leverageInfo, chunkRebalanceNotional);
} else {
(
chunkRebalanceNotional,
totalRebalanceNotional
) = _calculateChunkRebalanceNotional(_leverageInfo, _newLeverageRatio, true);
_lever(_leverageInfo, chunkRebalanceNotional);
}
return (chunkRebalanceNotional, totalRebalanceNotional);
}
/**
* Create the leverage info struct to be used in internal functions
*
* return LeverageInfo Struct containing ActionInfo and other data
*/
function _getAndValidateLeveragedInfo(uint256 _slippageTolerance, uint256 _maxTradeSize, string memory _exchangeName) internal view returns(LeverageInfo memory) {
// Assume if maxTradeSize is 0, then the exchange is not enabled. This is enforced by addEnabledExchange and updateEnabledExchange
require(_maxTradeSize > 0, "Must be valid exchange");
ActionInfo memory actionInfo = _createActionInfo();
require(actionInfo.setTotalSupply > 0, "SetToken must have > 0 supply");
require(actionInfo.collateralBalance > 0, "Collateral balance must be > 0");
require(actionInfo.borrowBalance > 0, "Borrow balance must exist");
// Get current leverage ratio
uint256 currentLeverageRatio = _calculateCurrentLeverageRatio(
actionInfo.collateralValue,
actionInfo.borrowValue
);
return LeverageInfo({
action: actionInfo,
currentLeverageRatio: currentLeverageRatio,
slippageTolerance: _slippageTolerance,
twapMaxTradeSize: _maxTradeSize,
exchangeName: _exchangeName
});
}
/**
* Create the action info struct to be used in internal functions
*
* return ActionInfo Struct containing data used by internal lever and delever functions
*/
function _createActionInfo() internal view virtual returns(ActionInfo memory) {
ActionInfo memory rebalanceInfo;
// Calculate prices from chainlink. Chainlink returns prices with 8 decimal places, but we need 36 - underlyingDecimals decimal places.
// This is so that when the underlying amount is multiplied by the received price, the collateral valuation is normalized to 36 decimals.
// To perform this adjustment, we multiply by 10^(36 - 8 - underlyingDecimals)
int256 rawCollateralPrice = strategy.collateralPriceOracle.latestAnswer();
rebalanceInfo.collateralPrice = rawCollateralPrice.toUint256().mul(10 ** strategy.collateralDecimalAdjustment);
int256 rawBorrowPrice = strategy.borrowPriceOracle.latestAnswer();
rebalanceInfo.borrowPrice = rawBorrowPrice.toUint256().mul(10 ** strategy.borrowDecimalAdjustment);
rebalanceInfo.collateralBalance = strategy.targetCollateralAToken.balanceOf(address(strategy.setToken));
rebalanceInfo.borrowBalance = strategy.targetBorrowDebtToken.balanceOf(address(strategy.setToken));
rebalanceInfo.collateralValue = rebalanceInfo.collateralPrice.preciseMul(rebalanceInfo.collateralBalance);
rebalanceInfo.borrowValue = rebalanceInfo.borrowPrice.preciseMul(rebalanceInfo.borrowBalance);
rebalanceInfo.setTotalSupply = strategy.setToken.totalSupply();
return rebalanceInfo;
}
/**
* Validate non-exchange settings in constructor and setters when updating.
*/
function _validateNonExchangeSettings(
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive
)
internal
virtual
pure
{
require (
_methodology.minLeverageRatio <= _methodology.targetLeverageRatio && _methodology.minLeverageRatio > 0,
"Must be valid min leverage"
);
require (
_methodology.maxLeverageRatio >= _methodology.targetLeverageRatio,
"Must be valid max leverage"
);
require (
_methodology.recenteringSpeed <= PreciseUnitMath.preciseUnit() && _methodology.recenteringSpeed > 0,
"Must be valid recentering speed"
);
require (
_execution.unutilizedLeveragePercentage <= PreciseUnitMath.preciseUnit(),
"Unutilized leverage must be <100%"
);
require (
_execution.slippageTolerance <= PreciseUnitMath.preciseUnit(),
"Slippage tolerance must be <100%"
);
require (
_incentive.incentivizedSlippageTolerance <= PreciseUnitMath.preciseUnit(),
"Incentivized slippage tolerance must be <100%"
);
require (
_incentive.incentivizedLeverageRatio >= _methodology.maxLeverageRatio,
"Incentivized leverage ratio must be > max leverage ratio"
);
require (
_methodology.rebalanceInterval >= _execution.twapCooldownPeriod,
"Rebalance interval must be greater than TWAP cooldown period"
);
require (
_execution.twapCooldownPeriod >= _incentive.incentivizedTwapCooldownPeriod,
"TWAP cooldown must be greater than incentivized TWAP cooldown"
);
}
/**
* Validate an ExchangeSettings struct when adding or updating an exchange. Does not validate that twapMaxTradeSize < incentivizedMaxTradeSize since
* it may be useful to disable exchanges for ripcord by setting incentivizedMaxTradeSize to 0.
*/
function _validateExchangeSettings(ExchangeSettings memory _settings) internal pure {
require(_settings.twapMaxTradeSize != 0, "Max TWAP trade size must not be 0");
}
/**
* Validate that current leverage is below incentivized leverage ratio and cooldown / rebalance period has elapsed or outsize max/min bounds. Used
* in rebalance() and iterateRebalance() functions
*/
function _validateNormalRebalance(LeverageInfo memory _leverageInfo, uint256 _coolDown, uint256 _lastTradeTimestamp) internal view {
require(_leverageInfo.currentLeverageRatio < incentive.incentivizedLeverageRatio, "Must be below incentivized leverage ratio");
require(
block.timestamp.sub(_lastTradeTimestamp) > _coolDown
|| _leverageInfo.currentLeverageRatio > methodology.maxLeverageRatio
|| _leverageInfo.currentLeverageRatio < methodology.minLeverageRatio,
"Cooldown not elapsed or not valid leverage ratio"
);
}
/**
* Validate that current leverage is above incentivized leverage ratio and incentivized cooldown period has elapsed in ripcord()
*/
function _validateRipcord(LeverageInfo memory _leverageInfo, uint256 _lastTradeTimestamp) internal view {
require(_leverageInfo.currentLeverageRatio >= incentive.incentivizedLeverageRatio, "Must be above incentivized leverage ratio");
// If currently in the midst of a TWAP rebalance, ensure that the cooldown period has elapsed
require(_lastTradeTimestamp.add(incentive.incentivizedTwapCooldownPeriod) < block.timestamp, "TWAP cooldown must have elapsed");
}
/**
* Validate TWAP in the iterateRebalance() function
*/
function _validateTWAP() internal view {
require(twapLeverageRatio > 0, "Not in TWAP state");
}
/**
* Validate not TWAP in the rebalance() function
*/
function _validateNonTWAP() internal view {
require(twapLeverageRatio == 0, "Must call iterate");
}
/**
* Check if price has moved advantageously while in the midst of the TWAP rebalance. This means the current leverage ratio has moved over/under
* the stored TWAP leverage ratio on lever/delever so there is no need to execute a rebalance. Used in iterateRebalance()
*/
function _isAdvantageousTWAP(uint256 _currentLeverageRatio) internal view returns (bool) {
return (
(twapLeverageRatio < methodology.targetLeverageRatio && _currentLeverageRatio >= twapLeverageRatio)
|| (twapLeverageRatio > methodology.targetLeverageRatio && _currentLeverageRatio <= twapLeverageRatio)
);
}
/**
* Calculate the current leverage ratio given a valuation of the collateral and borrow asset, which is calculated as collateral USD valuation / SetToken USD valuation
*
* return uint256 Current leverage ratio
*/
function _calculateCurrentLeverageRatio(
uint256 _collateralValue,
uint256 _borrowValue
)
internal
pure
returns(uint256)
{
return _collateralValue.preciseDiv(_collateralValue.sub(_borrowValue));
}
/**
* Calculate the new leverage ratio. The methodology reduces the size of each rebalance by weighting
* the current leverage ratio against the target leverage ratio by the recentering speed percentage. The lower the recentering speed, the slower
* the leverage token will move towards the target leverage each rebalance.
*
* return uint256 New leverage ratio
*/
function _calculateNewLeverageRatio(uint256 _currentLeverageRatio) internal view returns(uint256) {
// CLRt+1 = max(MINLR, min(MAXLR, CLRt * (1 - RS) + TLR * RS))
// a: TLR * RS
// b: (1- RS) * CLRt
// c: (1- RS) * CLRt + TLR * RS
// d: min(MAXLR, CLRt * (1 - RS) + TLR * RS)
uint256 a = methodology.targetLeverageRatio.preciseMul(methodology.recenteringSpeed);
uint256 b = PreciseUnitMath.preciseUnit().sub(methodology.recenteringSpeed).preciseMul(_currentLeverageRatio);
uint256 c = a.add(b);
uint256 d = Math.min(c, methodology.maxLeverageRatio);
return Math.max(methodology.minLeverageRatio, d);
}
/**
* Calculate total notional rebalance quantity and chunked rebalance quantity in collateral units.
*
* return uint256 Chunked rebalance notional in collat...
// [truncated — 60827 bytes total]
AaveV3LeverageStrategyExtension.sol 273 lines
/*
Copyright © 2023 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { AaveLeverageStrategyExtension } from "./AaveLeverageStrategyExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IAaveOracle } from "../interfaces/IAaveOracle.sol";
import { IPool } from "../interfaces/IPool.sol";
import { IPoolAddressesProvider } from "../interfaces/IPoolAddressesProvider.sol";
import { DataTypes } from "../interfaces/Datatypes.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title AaveV3LeverageStrategyExtension
* @author Index Coop
*
* Extension of AaveLeverageStrategyExtension to add endpoint for setting the eMode categoryId
*
*/
contract AaveV3LeverageStrategyExtension is AaveLeverageStrategyExtension {
uint8 public currentEModeCategoryId; // EMode CategoryId currently set on Aave for the SetToken
IPoolAddressesProvider public lendingPoolAddressesProvider; // Aave's address registry used to get Pool and Oracle addresses
bool public overrideNoRebalanceInProgress; // Manager controlled flag that allows bypassing the noRebalanceInProgress modifier
/* ============ Constructor ============ */
/**
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
*
* @param _manager Address of IBaseManager contract
* @param _strategy Struct of contract addresses
* @param _methodology Struct containing methodology parameters
* @param _execution Struct containing execution parameters
* @param _incentive Struct containing incentive parameters for ripcord
* @param _exchangeNames List of initial exchange names
* @param _exchangeSettings List of structs containing exchange parameters for the initial exchanges
* @param _lendingPoolAddressesProvider Aave's address registry used to get Pool and Oracle addresses
*/
constructor(
IBaseManager _manager,
ContractSettings memory _strategy,
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive,
string[] memory _exchangeNames,
ExchangeSettings[] memory _exchangeSettings,
IPoolAddressesProvider _lendingPoolAddressesProvider
)
public
AaveLeverageStrategyExtension(
_manager,
_strategy,
_methodology,
_execution,
_incentive,
_exchangeNames,
_exchangeSettings
)
{
lendingPoolAddressesProvider = _lendingPoolAddressesProvider;
}
/* ============ Modifiers ============ */
/**
* Throws if rebalance is currently in TWAP` can be overriden by the operator
*/
modifier noRebalanceInProgress() override {
if(!overrideNoRebalanceInProgress) {
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
}
_;
}
/* ============ External Functions ============ */
/**
* OPERATOR ONLY: Enable/Disable override of noRebalanceInProgress modifier
*
* @param _overrideNoRebalanceInProgress Boolean indicating wether to enable / disable override
*/
function setOverrideNoRebalanceInProgress(bool _overrideNoRebalanceInProgress) external onlyOperator {
overrideNoRebalanceInProgress = _overrideNoRebalanceInProgress;
}
/**
* OPERATOR ONLY: Set eMode categoryId to new value
*
* @param _categoryId eMode categoryId as defined on aaveV3
*/
function setEModeCategory(uint8 _categoryId) external onlyOperator {
currentEModeCategoryId = _categoryId;
_setEModeCategory(_categoryId);
}
/* ============ Internal Functions ============ */
/**
* Sets EMode category in AaveV3 on behalf of the SetToken
*/
function _setEModeCategory(uint8 _categoryId) internal {
bytes memory setEmodeCallData =
abi.encodeWithSignature("setEModeCategory(address,uint8)", address(strategy.setToken), _categoryId);
invokeManager(address(strategy.leverageModule), setEmodeCallData);
}
/**
* Calculate the max borrow / repay amount allowed in base units for lever / delever. This is due to overcollateralization requirements on
* assets deposited in lending protocols for borrowing.
*
*/
function _calculateMaxBorrowCollateral(ActionInfo memory _actionInfo, bool _isLever) internal override view returns(uint256) {
(uint256 maxLtvRaw, uint256 liquidationThresholdRaw) = _getLtvAndLiquidationThreshold();
// Normalize LTV and liquidation threshold to precise units. LTV is measured in 4 decimals in Aave which is why we must multiply by 1e14
// for example ETH has an LTV value of 8000 which represents 80%
if (_isLever) {
uint256 netBorrowLimit = _actionInfo.collateralValue
.preciseMul(maxLtvRaw.mul(10 ** 14))
.preciseMul(PreciseUnitMath.preciseUnit().sub(execution.unutilizedLeveragePercentage));
return netBorrowLimit
.sub(_actionInfo.borrowValue)
.preciseDiv(_actionInfo.collateralPrice);
} else {
uint256 netRepayLimit = _actionInfo.collateralValue
.preciseMul(liquidationThresholdRaw.mul(10 ** 14));
return _actionInfo.collateralBalance
.preciseMul(netRepayLimit.sub(_actionInfo.borrowValue))
.preciseDiv(netRepayLimit);
}
}
/**
* Calculates LTV and LquidationThreshold either based on ReserveConfiguration or EModeCategory depending on wether EMode is enabled or not
*
*/
function _getLtvAndLiquidationThreshold() internal view returns(uint256, uint256) {
if(currentEModeCategoryId != 0 ) {
// Retrieve collateral factor and liquidation threshold for the collateral asset in precise units (1e16 = 1%)
DataTypes.EModeCategory memory emodeData = IPool(lendingPoolAddressesProvider.getPool()).getEModeCategoryData(currentEModeCategoryId);
return (emodeData.ltv, emodeData.liquidationThreshold);
} else {
( , uint256 maxLtvRaw, uint256 liquidationThresholdRaw, , , , , , ,) = strategy.aaveProtocolDataProvider.getReserveConfigurationData(address(strategy.collateralAsset));
return (maxLtvRaw, liquidationThresholdRaw);
}
}
/**
* Validate non-exchange settings in constructor and setters when updating.
*/
function _validateNonExchangeSettings(
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive
)
internal
override
pure
{
super._validateNonExchangeSettings(_methodology, _execution, _incentive);
require(_methodology.targetLeverageRatio >= 1 ether, "Target leverage ratio must be >= 1e18");
}
/**
* Derive the min repay units from collateral units for delever. Units are calculated as target collateral rebalance units multiplied by slippage tolerance
* and pair price (collateral oracle price / borrow oracle price). Output is measured in borrow unit decimals.
*
* return uint256 Min position units to repay in borrow asset
*/
function _calculateMinRepayUnits(uint256 _collateralRebalanceUnits, uint256 _slippageTolerance, ActionInfo memory _actionInfo)
internal
override
pure
returns(uint256)
{
return _collateralRebalanceUnits
.preciseMul(_actionInfo.collateralPrice)
.preciseMul(PreciseUnitMath.preciseUnit().sub(_slippageTolerance)) // Changed order of mul / div here
.preciseDiv(_actionInfo.borrowPrice);
}
/**
* Calculate total notional rebalance quantity and chunked rebalance quantity in collateral units.
*
* return uint256 Chunked rebalance notional in collateral units
* return uint256 Total rebalance notional in collateral units
*/
function _calculateChunkRebalanceNotional(
LeverageInfo memory _leverageInfo,
uint256 _newLeverageRatio,
bool _isLever
)
internal
view
override
returns (uint256, uint256)
{
// Calculate absolute value of difference between new and current leverage ratio
uint256 leverageRatioDifference = _isLever ?
_newLeverageRatio.sub(_leverageInfo.currentLeverageRatio) :
_leverageInfo.currentLeverageRatio.sub(_newLeverageRatio);
uint256 totalRebalanceNotional = leverageRatioDifference
.preciseMul(_leverageInfo.action.collateralBalance) // Changed order of mul / div here
.preciseDiv(_leverageInfo.currentLeverageRatio);
uint256 maxBorrow = _calculateMaxBorrowCollateral(_leverageInfo.action, _isLever);
uint256 chunkRebalanceNotional = Math.min(Math.min(maxBorrow, totalRebalanceNotional), _leverageInfo.twapMaxTradeSize);
return (chunkRebalanceNotional, totalRebalanceNotional);
}
/**
* Create the action info struct to be used in internal functions
*
* return ActionInfo Struct containing data used by internal lever and delever functions
*/
function _createActionInfo() internal view override returns(ActionInfo memory) {
ActionInfo memory rebalanceInfo;
// Calculate prices from chainlink. AaveOracle returns prices with 8 decimal places, but we need 36 - underlyingDecimals decimal places.
// This is so that when the underlying amount is multiplied by the received price, the collateral valuation is normalized to 36 decimals.
// To perform this adjustment, we multiply by 10^(36 - 8 - underlyingDecimals)
rebalanceInfo.collateralPrice = _getAssetPrice(strategy.collateralAsset, strategy.collateralDecimalAdjustment);
rebalanceInfo.borrowPrice = _getAssetPrice(strategy.borrowAsset, strategy.borrowDecimalAdjustment);
rebalanceInfo.collateralBalance = strategy.targetCollateralAToken.balanceOf(address(strategy.setToken));
rebalanceInfo.borrowBalance = strategy.targetBorrowDebtToken.balanceOf(address(strategy.setToken));
rebalanceInfo.collateralValue = rebalanceInfo.collateralPrice.preciseMul(rebalanceInfo.collateralBalance);
rebalanceInfo.borrowValue = rebalanceInfo.borrowPrice.preciseMul(rebalanceInfo.borrowBalance);
rebalanceInfo.setTotalSupply = strategy.setToken.totalSupply();
return rebalanceInfo;
}
/**
* Gets AssetPrice from AaveOracle and multiplies by decimalAdjustment if necessary
*
* return uint256 Asset price normalized to desired number of decimals
*/
function _getAssetPrice(address _asset, uint256 _decimalAdjustment) internal view returns (uint256) {
IAaveOracle aaveOracle = IAaveOracle(IPoolAddressesProvider(lendingPoolAddressesProvider).getPriceOracle());
uint256 rawPrice = aaveOracle.getAssetPrice(_asset);
return rawPrice.mul(10 ** _decimalAdjustment);
}
}
AirdropExtension.sol 129 lines
/*
Copyright 2021 Index Cooperative.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IAirdropModule } from "../interfaces/IAirdropModule.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title AirdropExtension
* @author Index Coop
*
* Manager extension for interacting with AirdropModule
*/
contract AirdropExtension is BaseExtension {
/* ========== State Variables ========= */
// Address of AirdropModule
IAirdropModule public immutable airdropModule;
// Address of Set Token
ISetToken public immutable setToken;
/* ============ Constructor ============ */
/**
* Sets state variables
*
* @param _manager Manager contract
* @param _airdropModule Set Protocol AirdropModule
*/
constructor(IBaseManager _manager, IAirdropModule _airdropModule) public BaseExtension(_manager) {
airdropModule = _airdropModule;
setToken = manager.setToken();
}
/* ========== External Functions ========== */
/**
* OPERATOR ONLY: initializes the AirdropModule. The recipient is always set to the manager and the fee to 0.
*
* @param _airdropSettings Settings to initially the AirdropModule with
*/
function initializeAirdropModule(IAirdropModule.AirdropSettings memory _airdropSettings) external onlyOperator {
invokeManager(
address(airdropModule),
abi.encodeWithSignature("initialize(address,(address[],address,uint256,bool))", setToken, _airdropSettings)
);
}
/**
* OPERATOR ONLY: absorbs airdropped tokens
*
* @param _token Airdropped token to absorb
*/
function absorb(address _token) external onlyAllowedCaller(msg.sender) {
invokeManager(
address(airdropModule),
abi.encodeWithSignature("absorb(address,address)", setToken, _token)
);
}
/**
* OPERATOR ONLY: batch absorbs airdropped tokens
*
* @param _tokens List of airdropped tokens to absorb
*/
function batchAbsorb(address[] memory _tokens) external onlyAllowedCaller(msg.sender) {
invokeManager(
address(airdropModule),
abi.encodeWithSignature("batchAbsorb(address,address[])", setToken, _tokens)
);
}
/**
* OPERATOR ONLY: adds a new airdrop token
*
* @param _token Airdropped token to add
*/
function addAirdrop(address _token) external onlyOperator {
invokeManager(
address(airdropModule),
abi.encodeWithSignature("addAirdrop(address,address)", setToken, _token)
);
}
/**
* OPERATOR ONLY: removes a new airdrop token
*
* @param _token Airdropped token to remove
*/
function removeAirdrop(address _token) external onlyOperator {
invokeManager(
address(airdropModule),
abi.encodeWithSignature("removeAirdrop(address,address)", setToken, _token)
);
}
/**
* OPERATOR ONLY: updates the anyoneAbsorb setting
*
* @param _anyoneAbsorb new anyoneAbsorb setting value
*/
function updateAnyoneAbsorb(bool _anyoneAbsorb) external onlyOperator {
invokeManager(
address(airdropModule),
abi.encodeWithSignature("updateAnyoneAbsorb(address,bool)", setToken, _anyoneAbsorb)
);
}
}
AuctionRebalanceExtension.sol 198 lines
/*
Copyright 2023 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title AuctionRebalanceExtension
* @author Index Coop
*
* @dev Extension contract for interacting with the AuctionRebalanceModuleV1. This contract acts as a pass-through and functions
* are only callable by operator.
*/
contract AuctionRebalanceExtension is BaseExtension {
using AddressArrayUtils for address[];
using SafeMath for uint256;
/* ============ Structs ============ */
struct AuctionExecutionParams {
uint256 targetUnit; // Target quantity of the component in Set, in precise units (10 ** 18).
string priceAdapterName; // Identifier for the price adapter to be used.
bytes priceAdapterConfigData; // Encoded data for configuring the chosen price adapter.
}
/* ============ State Variables ============ */
ISetToken public immutable setToken;
IAuctionRebalanceModuleV1 public immutable auctionModule; // AuctionRebalanceModuleV1
/* ============ Constructor ============ */
constructor(IBaseManager _manager, IAuctionRebalanceModuleV1 _auctionModule) public BaseExtension(_manager) {
auctionModule = _auctionModule;
setToken = manager.setToken();
}
/* ============ External Functions ============ */
/**
* @dev OPERATOR ONLY: Checks that the old components array matches the current components array and then invokes the
* AuctionRebalanceModuleV1 startRebalance function.
*
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _quoteAsset ERC20 token used as the quote asset in auctions.
* @param _oldComponents Addresses of existing components in the SetToken.
* @param _newComponents Addresses of new components to be added.
* @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents.
* @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to
* the current component positions. Set to 0 for components being removed.
* @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken.
* @param _rebalanceDuration Duration of the rebalance in seconds.
* @param _positionMultiplier Position multiplier at the time target units were calculated.
*/
function startRebalance(
IERC20 _quoteAsset,
address[] memory _oldComponents,
address[] memory _newComponents,
AuctionExecutionParams[] memory _newComponentsAuctionParams,
AuctionExecutionParams[] memory _oldComponentsAuctionParams,
bool _shouldLockSetToken,
uint256 _rebalanceDuration,
uint256 _positionMultiplier
)
external
virtual
onlyOperator
{
address[] memory currentComponents = setToken.getComponents();
require(currentComponents.length == _oldComponents.length, "Old components length must match the current components length.");
for (uint256 i = 0; i < _oldComponents.length; i++) {
require(currentComponents[i] == _oldComponents[i], "Input old components array must match the current components array.");
}
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.startRebalance.selector,
setToken,
_quoteAsset,
_newComponents,
_newComponentsAuctionParams,
_oldComponentsAuctionParams,
_shouldLockSetToken,
_rebalanceDuration,
_positionMultiplier
);
invokeManager(address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Unlocks SetToken via AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*/
function unlock() external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.unlock.selector,
setToken
);
invokeManager(address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Sets the target raise percentage for all components on AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _raiseTargetPercentage Amount to raise all component's unit targets by (in precise units)
*/
function setRaiseTargetPercentage(uint256 _raiseTargetPercentage) external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.setRaiseTargetPercentage.selector,
setToken,
_raiseTargetPercentage
);
invokeManager(address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Updates the bidding permission status for a list of addresses on AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _bidders An array of addresses whose bidding permission status is to be toggled.
* @param _statuses An array of booleans indicating the new bidding permission status for each corresponding address in `_bidders`.
*/
function setBidderStatus(
address[] memory _bidders,
bool[] memory _statuses
)
external
onlyOperator
{
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.setBidderStatus.selector,
setToken,
_bidders,
_statuses
);
invokeManager(address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Sets whether anyone can bid on the AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _status A boolean indicating if anyone can bid.
*/
function setAnyoneBid(bool _status) external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.setAnyoneBid.selector,
setToken,
_status
);
invokeManager(address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Initializes the AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*/
function initialize() external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.initialize.selector,
setToken
);
invokeManager(address(auctionModule), callData);
}
}
FeeSplitExtension.sol 283 lines
/*
Copyright 2021 IndexCooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IIssuanceModule } from "../interfaces/IIssuanceModule.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { TimeLockUpgrade } from "../lib/TimeLockUpgrade.sol";
import { MutualUpgrade } from "../lib/MutualUpgrade.sol";
/**
* @title FeeSplitExtension
* @author Set Protocol
*
* Smart contract extension that allows for splitting and setting streaming and mint/redeem fees.
*/
contract FeeSplitExtension is BaseExtension, TimeLockUpgrade, MutualUpgrade {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Events ============ */
event FeesDistributed(
address indexed _operatorFeeRecipient,
address indexed _methodologist,
uint256 _operatorTake,
uint256 _methodologistTake
);
/* ============ State Variables ============ */
ISetToken public setToken;
IStreamingFeeModule public streamingFeeModule;
IIssuanceModule public issuanceModule;
// Percent of fees in precise units (10^16 = 1%) sent to operator, rest to methodologist
uint256 public operatorFeeSplit;
// Address which receives operator's share of fees when they're distributed. (See IIP-72)
address public operatorFeeRecipient;
/* ============ Constructor ============ */
constructor(
IBaseManager _manager,
IStreamingFeeModule _streamingFeeModule,
IIssuanceModule _issuanceModule,
uint256 _operatorFeeSplit,
address _operatorFeeRecipient
)
public
BaseExtension(_manager)
{
streamingFeeModule = _streamingFeeModule;
issuanceModule = _issuanceModule;
operatorFeeSplit = _operatorFeeSplit;
operatorFeeRecipient = _operatorFeeRecipient;
setToken = manager.setToken();
}
/* ============ External Functions ============ */
/**
* ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for
* operator and methodologist, and sends to operator fee recipient and methodologist respectively. NOTE: mint/redeem fees
* will automatically be sent to this address so reading the balance of the SetToken in the contract after accrual is
* sufficient for accounting for all collected fees.
*/
function accrueFeesAndDistribute() public virtual {
// Emits a FeeActualized event
streamingFeeModule.accrueFee(setToken);
uint256 totalFees = setToken.balanceOf(address(this));
address methodologist = manager.methodologist();
uint256 operatorTake = totalFees.preciseMul(operatorFeeSplit);
uint256 methodologistTake = totalFees.sub(operatorTake);
if (operatorTake > 0) {
setToken.transfer(operatorFeeRecipient, operatorTake);
}
if (methodologistTake > 0) {
setToken.transfer(methodologist, methodologistTake);
}
emit FeesDistributed(operatorFeeRecipient, methodologist, operatorTake, methodologistTake);
}
/**
* MUTUAL UPGRADE: Initializes the issuance module. Operator and Methodologist must each call
* this function to execute the update.
*
* This method is called after invoking `replaceProtectedModule` or `emergencyReplaceProtectedModule`
* to configure the replacement streaming fee module's fee settings.
*
* @param _maxManagerFee Max size of issuance and redeem fees in precise units (10^16 = 1%).
* @param _managerIssueFee Manager issuance fees in precise units (10^16 = 1%)
* @param _managerRedeemFee Manager redeem fees in precise units (10^16 = 1%)
* @param _feeRecipient Address that receives all manager issue and redeem fees
* @param _managerIssuanceHook Address of manager defined hook contract
*/
function initializeIssuanceModule(
uint256 _maxManagerFee,
uint256 _managerIssueFee,
uint256 _managerRedeemFee,
address _feeRecipient,
address _managerIssuanceHook
)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
bytes memory callData = abi.encodeWithSelector(
IIssuanceModule.initialize.selector,
manager.setToken(),
_maxManagerFee,
_managerIssueFee,
_managerRedeemFee,
_feeRecipient,
_managerIssuanceHook
);
invokeManager(address(issuanceModule), callData);
}
/**
* MUTUAL UPGRADE: Initializes the issuance module. Operator and Methodologist must each call
* this function to execute the update.
*
* This method is called after invoking `replaceProtectedModule` or `emergencyReplaceProtectedModule`
* to configure the replacement streaming fee module's fee settings.
*
* @dev FeeState settings encode the following struct
* ```
* struct FeeState {
* address feeRecipient; // Address to accrue fees to
* uint256 maxStreamingFeePercentage; // Max streaming fee maanager commits to using (1% = 1e16, 100% = 1e18)
* uint256 streamingFeePercentage; // Percent of Set accruing to manager annually (1% = 1e16, 100% = 1e18)
* uint256 lastStreamingFeeTimestamp; // Timestamp last streaming fee was accrued
*}
*```
* @param _settings FeeModule.FeeState settings
*/
function initializeStreamingFeeModule(IStreamingFeeModule.FeeState memory _settings)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
bytes memory callData = abi.encodeWithSelector(
IStreamingFeeModule.initialize.selector,
manager.setToken(),
_settings
);
invokeManager(address(streamingFeeModule), callData);
}
/**
* MUTUAL UPGRADE: Updates streaming fee on StreamingFeeModule. Operator and Methodologist must each call
* this function to execute the update. Because the method is timelocked, each party must call it twice:
* once to set the lock and once to execute.
*
* Method is timelocked to protect token owners from sudden changes in fee structure which
* they would rather not bear. The delay gives them a chance to exit their positions without penalty.
*
* NOTE: This will accrue streaming fees though not send to operator fee recipient and methodologist.
*
* @param _newFee Percent of Set accruing to fee extension annually (1% = 1e16, 100% = 1e18)
*/
function updateStreamingFee(uint256 _newFee)
external
mutualUpgrade(manager.operator(), manager.methodologist())
timeLockUpgrade
{
bytes memory callData = abi.encodeWithSignature("updateStreamingFee(address,uint256)", manager.setToken(), _newFee);
invokeManager(address(streamingFeeModule), callData);
}
/**
* MUTUAL UPGRADE: Updates issue fee on IssuanceModule. Only is executed once time lock has passed.
* Operator and Methodologist must each call this function to execute the update. Because the method
* is timelocked, each party must call it twice: once to set the lock and once to execute.
*
* Method is timelocked to protect token owners from sudden changes in fee structure which
* they would rather not bear. The delay gives them a chance to exit their positions without penalty.
*
* @param _newFee New issue fee percentage in precise units (1% = 1e16, 100% = 1e18)
*/
function updateIssueFee(uint256 _newFee)
external
mutualUpgrade(manager.operator(), manager.methodologist())
timeLockUpgrade
{
bytes memory callData = abi.encodeWithSignature("updateIssueFee(address,uint256)", manager.setToken(), _newFee);
invokeManager(address(issuanceModule), callData);
}
/**
* MUTUAL UPGRADE: Updates redeem fee on IssuanceModule. Only is executed once time lock has passed.
* Operator and Methodologist must each call this function to execute the update. Because the method is
* timelocked, each party must call it twice: once to set the lock and once to execute.
*
* Method is timelocked to protect token owners from sudden changes in fee structure which
* they would rather not bear. The delay gives them a chance to exit their positions without penalty.
*
* @param _newFee New redeem fee percentage in precise units (1% = 1e16, 100% = 1e18)
*/
function updateRedeemFee(uint256 _newFee)
external
mutualUpgrade(manager.operator(), manager.methodologist())
timeLockUpgrade
{
bytes memory callData = abi.encodeWithSignature("updateRedeemFee(address,uint256)", manager.setToken(), _newFee);
invokeManager(address(issuanceModule), callData);
}
/**
* MUTUAL UPGRADE: Updates fee recipient on both streaming fee and issuance modules.
*
* @param _newFeeRecipient Address of new fee recipient. This should be the address of the fee extension itself.
*/
function updateFeeRecipient(address _newFeeRecipient)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
bytes memory callData = abi.encodeWithSignature("updateFeeRecipient(address,address)", manager.setToken(), _newFeeRecipient);
invokeManager(address(streamingFeeModule), callData);
invokeManager(address(issuanceModule), callData);
}
/**
* MUTUAL UPGRADE: Updates fee split between operator and methodologist. Split defined in precise units (1% = 10^16).
*
* @param _newFeeSplit Percent of fees in precise units (10^16 = 1%) sent to operator, (rest go to the methodologist).
*/
function updateFeeSplit(uint256 _newFeeSplit)
external
virtual
mutualUpgrade(manager.operator(), manager.methodologist())
{
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%");
accrueFeesAndDistribute();
operatorFeeSplit = _newFeeSplit;
}
/**
* OPERATOR ONLY: Updates the address that receives the operator's share of the fees (see IIP-72)
*
* @param _newOperatorFeeRecipient Address to send operator's fees to.
*/
function updateOperatorFeeRecipient(address _newOperatorFeeRecipient)
external
onlyOperator
{
require(_newOperatorFeeRecipient != address(0), "Zero address not valid");
operatorFeeRecipient = _newOperatorFeeRecipient;
}
}
FixedRebalanceExtension.sol 440 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ICErc20 } from "../interfaces/ICErc20.sol";
import { INotionalTradeModule } from "../interfaces/INotionalTradeModule.sol";
import { INotionalProxy, MarketParameters } from "../interfaces/INotionalProxy.sol";
import { IWrappedfCashComplete } from "../interfaces/IWrappedfCash.sol";
import { IWrappedfCashFactory } from "../interfaces/IWrappedfCashFactory.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title FixedRebalanceExtension
* @author IndexCoop
*
* Smart contract that enables rebalancing of FIXED products
* Will sell redeem fCash positions and mint underweight fCash positions via NotionalTradeModule
*/
contract FixedRebalanceExtension is BaseExtension {
using PreciseUnitMath for uint256;
using SafeMath for uint256;
// /* ============ Events ============ */
event AllocationsUpdated(uint256[] _maturities, uint256[] _allocations, uint256[] _minPositions);
// /* ============ State Variables ============ */
uint256[] internal maturities; // Array of relative maturities in seconds (i.e. 3 months / 6 months)
uint256[] internal allocations; // Relative allocations
uint256[] internal minPositions; // Minimum positions to achieve after every full rebalancing (assuming share = 100 %)
ISetToken public immutable setToken;
INotionalTradeModule internal immutable notionalTradeModule;
INotionalProxy internal immutable notionalProxy;
IWrappedfCashFactory internal immutable wrappedfCashFactory;
address internal immutable underlyingToken;
address internal immutable assetToken;
uint16 public immutable currencyId;
bool internal tradeViaUnderlying;
// /* ============ Constructor ============ */
constructor(
IBaseManager _manager,
ISetToken _setToken,
INotionalTradeModule _notionalTradeModule,
INotionalProxy _notionalProxy,
IWrappedfCashFactory _wrappedfCashFactory,
address _underlyingToken,
address _assetToken,
uint256[] memory _maturities,
uint256[] memory _allocations,
uint256[] memory _minPositions
)
public
BaseExtension(_manager)
{
setToken = _setToken;
notionalTradeModule = _notionalTradeModule;
notionalProxy = _notionalProxy;
wrappedfCashFactory = _wrappedfCashFactory;
underlyingToken = _underlyingToken;
assetToken = _assetToken;
currencyId = _notionalProxy.getCurrencyId(_assetToken);
_setAllocations(_maturities, _allocations, _minPositions);
}
// /* ============ External Functions ============ */
/**
* ONLY OPERATOR: Updates the relative maturities that are valid to allocate to
*
* @param _tradeViaUnderlying Boolean indicating whether or not to trade via the underlying token
*/
function setTradeViaUnderlying(bool _tradeViaUnderlying) external onlyOperator {
tradeViaUnderlying = _tradeViaUnderlying;
}
/**
* ONLY OPERATOR: Updates the relative maturities that are valid to allocate to
*
* @param _maturities Relative maturities (i.e. "3 months") in seconds
* @param _allocations Relative allocations (i.e. 0.9 = 90%) with 18 decimals corresponding to the respective maturity
*/
function setAllocations(uint256[] memory _maturities, uint256[] memory _allocations, uint256[] memory _minPositions) external onlyOperator {
_setAllocations(_maturities, _allocations, _minPositions);
}
/**
* ONLY ALLOWED CALLER: Rebalances the positions towards the configured allocation percentages.
*
* @param _share Relative share of the necessary trade volume to execute (allows for splitting the rebalance over multiple transactions
* @param _rebalanceMinPositions Minimum positions (in set token units) for each maturity after this rebalance operation.
* @dev Will revert if _rebalanceMinPositions is lower than the minPositions configured by the operator (weighted by share)
*/
function rebalance(uint256 _share, uint256[] memory _rebalanceMinPositions) external onlyAllowedCaller(msg.sender) returns(uint256[] memory) {
require(_share > 0, "Share must be greater than 0");
require(_share <= 1 ether, "Share cannot exceed 100%");
uint256[] memory currentPositionsBefore = _sellOverweightPositions(_share);
_buyUnderweightPositions(_share);
return _checkCurrentPositions(currentPositionsBefore, _rebalanceMinPositions, _share);
}
// Aggregates all fCash positions + asset token position into a single value
// @dev The value represents the current value of all relevant positions and can be multiplied by the relative allocation to calculate the target position size
function getTotalAllocation() external view returns(uint256) {
return _getTotalAllocation();
}
// Get positions that are currently below their targeted weight
function getUnderweightPositions() external view returns(uint256[] memory, uint256[] memory, uint256[] memory) {
return _getUnderweightPositions();
}
// Get absolute maturities corresponding to currently configured allocations
function getAbsoluteMaturities() external view returns (uint256[] memory absoluteMaturities) {
absoluteMaturities = new uint256[](maturities.length);
for(uint256 i = 0; i < maturities.length; i++) {
absoluteMaturities[i] = _relativeToAbsoluteMaturity(maturities[i]);
}
}
// Get maturities, allocations and minPositions
function getAllocations() external view returns (uint256[] memory, uint256[] memory, uint256[] memory) {
return (maturities, allocations, minPositions);
}
// Convert relative to aboslute maturity
function relativeToAbsoluteMaturity(uint256 _relativeMaturity) external view returns (uint256) {
return _relativeToAbsoluteMaturity(_relativeMaturity);
}
// Return current token position for each maturity;
function getCurrentPositions()
external
view
returns(uint256[] memory currentPositions)
{
currentPositions = new uint256[](maturities.length);
for(uint i = 0; i < maturities.length; i++) {
uint256 maturity = _relativeToAbsoluteMaturity(maturities[i]);
address wrappedfCash = wrappedfCashFactory.computeAddress(currencyId, uint40(maturity));
int256 currentPositionSigned = setToken.getDefaultPositionRealUnit(wrappedfCash);
require(currentPositionSigned >= 0, "Negative position");
currentPositions[i] = uint256(currentPositionSigned);
}
}
// /* ============ Internal Functions ============ */
// @dev Sells fCash positions that are currently above their targeted allocation
// @dev _maturity absolute maturity of fCash to redeem
// @param _share Relative share of the necessary trade volume to execute (allows for splitting the rebalance over multiple transactions
function _sellOverweightPositions(uint256 _share) internal returns(uint256[] memory){
(
uint256[] memory overweightPositions,
uint256[] memory currentPositions,
uint256[] memory absoluteMaturities
) = _getOverweightPositions();
for(uint256 i = 0; i < overweightPositions.length; i++) {
uint256 receiveAmount = overweightPositions[i].preciseMul(_share);
if(overweightPositions[i] > 0) {
_redeemFCash(absoluteMaturities[i], receiveAmount, currentPositions[i]);
}
}
return currentPositions;
}
// @dev Buys fCash positions that are currently below their targeted allocation
// @dev _maturity absolute maturity of fCash to redeem
// @param _share Relative share of the necessary trade volume to execute (allows for splitting the rebalance over multiple transactions
function _buyUnderweightPositions(uint256 _share) internal {
(
uint256[] memory underweightPositions,,
uint256[] memory absoluteMaturities
) = _getUnderweightPositions();
for(uint256 i = 0; i < underweightPositions.length; i++) {
uint256 sendAmount = underweightPositions[i].preciseMul(_share);
if(sendAmount > 0) {
_mintFCash(absoluteMaturities[i], sendAmount);
}
}
}
// @dev Checks that the positions after rebalance are above the _rebalanceMinPositions specified in rebalanceCall
// @dev Also verifies that _rebalanceMinPositions are above the minPositions configured by the operator (weighted by _share)
function _checkCurrentPositions(uint256[] memory _positionsBefore, uint256[] memory _rebalanceMinPositions, uint256 _share)
internal
view
returns(uint256[] memory currentPositions)
{
require(_rebalanceMinPositions.length == maturities.length , "Min positions must be same length as maturities");
currentPositions = new uint256[](maturities.length);
for(uint i = 0; i < maturities.length; i++) {
uint256 weightedMinPosition = _getWeightedMinPosition(minPositions[i], _positionsBefore[i], _share);
require(_rebalanceMinPositions[i] >= weightedMinPosition, "Caller provided min position must not be less than operator specified value weighted by share");
uint256 maturity = _relativeToAbsoluteMaturity(maturities[i]);
address wrappedfCash = wrappedfCashFactory.computeAddress(currencyId, uint40(maturity));
int256 currentPositionSigned = setToken.getDefaultPositionRealUnit(wrappedfCash);
require(currentPositionSigned >= 0, "Negative position");
require(uint256(currentPositionSigned) >= _rebalanceMinPositions[i], "Position below min");
currentPositions[i] = uint256(currentPositionSigned);
}
}
// @dev Calculates the minimumPosition for a given maturity by taking the weighted average between the _minPosition configured by the operator and the position before the rebalance call.
function _getWeightedMinPosition(uint256 _minPosition, uint256 _positionBefore, uint256 _share) internal pure returns(uint256) {
if(_minPosition > _positionBefore) {
// If the position was below the min position before you have to increase it by at least _share % of the difference
return _positionBefore.add(_minPosition.sub(_positionBefore).preciseMul(_share));
} else {
// If the position was above the min position before you can only decrease it by maximum _share % of the difference
return _positionBefore.sub(_positionBefore.sub(_minPosition).preciseMul(_share));
}
}
// @dev Get positions that are currently above their targeted weight
function _getOverweightPositions()
internal
view
returns(
uint256[] memory overweightPositions,
uint256[] memory currentPositions,
uint256[] memory absoluteMaturities
)
{
uint256 totalFCashAndUnderlyingPosition = _getTotalAllocation();
overweightPositions = new uint256[](maturities.length);
currentPositions = new uint256[](maturities.length);
absoluteMaturities = new uint256[](maturities.length);
for(uint i = 0; i < maturities.length; i++) {
uint256 maturity = _relativeToAbsoluteMaturity(maturities[i]);
absoluteMaturities[i] = maturity;
address wrappedfCash = wrappedfCashFactory.computeAddress(currencyId, uint40(maturity));
int256 currentPositionSigned = setToken.getDefaultPositionRealUnit(wrappedfCash);
require(currentPositionSigned >= 0, "Negative position");
uint256 currentPosition = _getCurrentValue(uint256(currentPositionSigned), maturity);
uint256 targetPosition = allocations[i].preciseMul(totalFCashAndUnderlyingPosition);
currentPositions[i] = uint256(currentPositionSigned);
if(currentPosition > targetPosition) {
overweightPositions[i] = currentPosition.sub(targetPosition);
}
}
}
// @dev Get positions that are currently below their targeted weight
function _getUnderweightPositions()
internal
view
returns(
uint256[] memory underweightPositions,
uint256[] memory currentPositions,
uint256[] memory absoluteMaturities
)
{
uint256 totalFCashAndUnderlyingPosition = _getTotalAllocation();
underweightPositions = new uint256[](maturities.length);
currentPositions = new uint256[](maturities.length);
absoluteMaturities = new uint256[](maturities.length);
for(uint i = 0; i < maturities.length; i++) {
uint256 maturity = _relativeToAbsoluteMaturity(maturities[i]);
absoluteMaturities[i] = maturity;
address wrappedfCash = wrappedfCashFactory.computeAddress(currencyId, uint40(maturity));
int256 currentPositionSigned = setToken.getDefaultPositionRealUnit(wrappedfCash);
require(currentPositionSigned >= 0, "Negative position");
uint256 currentPosition = _getCurrentValue(uint256(currentPositionSigned), maturity);
uint256 targetPosition = allocations[i].preciseMul(totalFCashAndUnderlyingPosition);
currentPositions[i] = uint256(currentPositionSigned);
if(currentPosition < targetPosition) {
underweightPositions[i] = targetPosition.sub(currentPosition);
}
}
}
// @dev Aggregates all fCash positions + asset token position into a single value
// @dev The value represents the current value of all relevant positions and can be multiplied by the relative allocation to calculate the target position size
function _getTotalAllocation() internal view returns(uint256) {
address tradeToken = _getTradeToken();
int256 tradeTokenPosition = setToken.getDefaultPositionRealUnit(tradeToken);
require(tradeTokenPosition >= 0, "Negative asset position");
return _getTotalFCashPosition().add(uint256(tradeTokenPosition));
}
// @dev Aggregates all fCash positions into a single value
// @dev Converts all fCashPosition to their equivalent assetToken value and returns the sum
function _getTotalFCashPosition() internal view returns(uint256 totalFCashPosition) {
address[] memory fCashComponents = notionalTradeModule.getFCashComponents(setToken);
for(uint256 i = 0; i < fCashComponents.length; i++) {
int256 currentPositionSigned = setToken.getDefaultPositionRealUnit(fCashComponents[i]);
require(currentPositionSigned >= 0, "Negative position");
uint256 maturity = IWrappedfCashComplete(fCashComponents[i]).getMaturity();
uint256 currentPositionDiscounted = _getCurrentValue(uint256(currentPositionSigned), maturity);
totalFCashPosition = totalFCashPosition.add(uint256(currentPositionDiscounted));
}
}
// @dev Converts a given amount of fcash at given maturity to the equivalent asset or underlying token value
// @dev Represents the current / discounted value of the fCash position
function _getCurrentValue(uint256 _amount, uint256 _maturity) internal view returns(uint256) {
(uint256 underlyingTokenAmount, uint256 assetTokenAmount,,) = notionalProxy.getDepositFromfCashLend(
currencyId,
_amount,
_maturity,
0,
block.timestamp
);
return tradeViaUnderlying ? underlyingTokenAmount : assetTokenAmount;
}
// @dev Mint fCash for asset token
// @dev _maturity absolute maturity of fCash to redeem
// @dev _tradeTokenAmount corresponding tradeToken amount for which to mint fCash
function _mintFCash(uint256 _maturity, uint256 _tradeTokenAmount) internal {
address tradeToken = _getTradeToken();
uint256 tradeTokenPosition = uint256(setToken.getDefaultPositionRealUnit(tradeToken));
require(tradeTokenPosition >= _tradeTokenAmount, "Insufficient asset token balance for mint");
// TODO: Check if we need to calculate a better value for slippage protection etc.
uint256 minFCashAmount = 0;
bytes memory callData = abi.encodeWithSignature(
"mintFCashForFixedToken(address,uint16,uint40,uint256,address,uint256)",
address(setToken),
currencyId,
uint40(_maturity),
minFCashAmount,
address(tradeToken),
_tradeTokenAmount
);
invokeManager(
address(notionalTradeModule),
callData
);
}
// @dev Redeems fCash for trade token
// @dev _maturity absolute maturity of fCash to redeem
// @dev _tradeTokenAmount corresponding tradeToken amount for which to redeem fCash
// @dev _fCashPosiiton current fCash position of the setToken
function _redeemFCash(
uint256 _maturity,
uint256 _tradeTokenAmount,
uint256 _fCashPosition
)
internal
{
address tradeToken = _getTradeToken();
bytes memory callData = abi.encodeWithSignature(
"redeemFCashForFixedToken(address,uint16,uint40,uint256,address,uint256,uint256)",
address(setToken),
currencyId,
uint40(_maturity),
_fCashPosition,
address(tradeToken),
_tradeTokenAmount,
0.01 ether
);
invokeManager(
address(notionalTradeModule),
callData
);
}
// @dev Converts relative to absolute maturity
// @param _relativeMaturity Relative maturity to convert (i.e. "3 months" in seconds)
// @return absoluteMaturity Absolute maturity to convert (i.e. "2022-12-22" in seconds)
// @dev Returns largest active maturity on notional that is smaller than the relative maturity
function _relativeToAbsoluteMaturity(uint256 _relativeMaturity) internal view returns (uint256 absoluteMaturity) {
MarketParameters[] memory activeMarkets = notionalProxy.getActiveMarkets(currencyId);
for(uint256 i = 0; i < activeMarkets.length; i++) {
if(activeMarkets[i].maturity > block.timestamp && (activeMarkets[i].maturity - block.timestamp) < _relativeMaturity) {
if(activeMarkets[i].maturity > absoluteMaturity) {
absoluteMaturity = activeMarkets[i].maturity;
}
}
}
require(absoluteMaturity > 0, "No active market found for given relative maturity");
}
// @dev Sets configured allocations for the setToken
function _setAllocations(uint256[] memory _maturities, uint256[] memory _allocations, uint256[] memory _minPositions) internal {
require((_maturities.length == _allocations.length) && (_maturities.length == _minPositions.length), "Maturities, minPositions and allocations must be same length");
uint256 totalAllocation = 0;
for (uint256 i = 0; i < _maturities.length; i++) {
totalAllocation = totalAllocation.add(_allocations[i]);
}
require(totalAllocation == PreciseUnitMath.preciseUnit(), "Allocations must sum to 1");
maturities = _maturities;
allocations = _allocations;
minPositions = _minPositions;
emit AllocationsUpdated(_maturities, _allocations, _minPositions);
}
// @dev Returns the token via which to trade
function _getTradeToken() internal view returns(address) {
return tradeViaUnderlying ? underlyingToken : assetToken;
}
}
FlexibleLeverageStrategyExtension.sol 1289 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { ICErc20 } from "../interfaces/ICErc20.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IChainlinkAggregatorV3 } from "../interfaces/IChainlinkAggregatorV3.sol";
import { IComptroller } from "../interfaces/IComptroller.sol";
import { ILeverageModule } from "../interfaces/ILeverageModule.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { StringArrayUtils } from "../lib/StringArrayUtils.sol";
/**
* @title FlexibleLeverageStrategyExtension
* @author Set Protocol
*
* Smart contract that enables trustless leverage tokens using the flexible leverage methodology. This extension is paired with the CompoundLeverageModule from Set
* protocol where module interactions are invoked via the IBaseManager contract. Any leveraged token can be constructed as long as the collateral and borrow
* asset is available on Compound. This extension contract also allows the operator to set an ETH reward to incentivize keepers calling the rebalance function at
* different leverage thresholds.
*
* CHANGELOG 4/14/2021:
* - Update ExecutionSettings struct to split exchangeData into leverExchangeData and deleverExchangeData
* - Update _lever and _delever internal functions with struct changes
* - Update setExecutionSettings to account for leverExchangeData and deleverExchangeData
*
* CHANGELOG 5/24/2021:
* - Update _calculateActionInfo to add chainlink prices
* - Update _calculateBorrowUnits and _calculateMinRepayUnits to use chainlink as an oracle in
*
* CHANGELOG 6/29/2021: c55bd3cdb0fd43c03da9904493dcc23771ef0f71
* - Add ExchangeSettings struct that contains exchange specific information
* - Update ExecutionSettings struct to not include exchange information
* - Add mapping of exchange names to ExchangeSettings structs and a list of enabled exchange names
* - Update constructor to take an array of exchange names and an array of ExchangeSettings
* - Add _exchangeName parameter to rebalancing functions to select which exchange to use
* - Add permissioned addEnabledExchange, updateEnabledExchange, and removeEnabledExchange functions
* - Add getChunkRebalanceNotional function
* - Update shouldRebalance and shouldRebalanceWithBounds to return an array of ShouldRebalance enums and an array of exchange names
* - Update _shouldRebalance to use exchange specific last trade timestamps
* - Update _validateRipcord and _validateNormalRebalance to take in a timestamp parameter (so we can pass either global or exchange specific timestamp)
* - Add _updateLastTradeTimestamp function to update global and exchange specific timestamp
* - Change contract name to FlexibleLeverageStrategyExtension
*/
contract FlexibleLeverageStrategyExtension is BaseExtension {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using SafeCast for int256;
using StringArrayUtils for string[];
/* ============ Enums ============ */
enum ShouldRebalance {
NONE, // Indicates no rebalance action can be taken
REBALANCE, // Indicates rebalance() function can be successfully called
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
RIPCORD // Indicates ripcord() function can be successfully called
}
/* ============ Structs ============ */
struct ActionInfo {
uint256 collateralBalance; // Balance of underlying held in Compound in base units (e.g. USDC 10e6)
uint256 borrowBalance; // Balance of underlying borrowed from Compound in base units
uint256 collateralValue; // Valuation in USD adjusted for decimals in precise units (10e18)
uint256 borrowValue; // Valuation in USD adjusted for decimals in precise units (10e18)
uint256 collateralPrice; // Price of collateral in precise units (10e18) from Chainlink
uint256 borrowPrice; // Price of borrow asset in precise units (10e18) from Chainlink
uint256 setTotalSupply; // Total supply of SetToken
}
struct LeverageInfo {
ActionInfo action;
uint256 currentLeverageRatio; // Current leverage ratio of Set
uint256 slippageTolerance; // Allowable percent trade slippage in preciseUnits (1% = 10^16)
uint256 twapMaxTradeSize; // Max trade size in collateral units allowed for rebalance action
string exchangeName; // Exchange to use for trade
}
struct ContractSettings {
ISetToken setToken; // Instance of leverage token
ILeverageModule leverageModule; // Instance of Compound leverage module
IComptroller comptroller; // Instance of Compound Comptroller
IChainlinkAggregatorV3 collateralPriceOracle; // Chainlink oracle feed that returns prices in 8 decimals for collateral asset
IChainlinkAggregatorV3 borrowPriceOracle; // Chainlink oracle feed that returns prices in 8 decimals for borrow asset
ICErc20 targetCollateralCToken; // Instance of target collateral cToken asset
ICErc20 targetBorrowCToken; // Instance of target borrow cToken asset
address collateralAsset; // Address of underlying collateral
address borrowAsset; // Address of underlying borrow asset
uint256 collateralDecimalAdjustment; // Decimal adjustment for chainlink oracle of the collateral asset. Equal to 28-collateralDecimals (10^18 * 10^18 / 10^decimals / 10^8)
uint256 borrowDecimalAdjustment; // Decimal adjustment for chainlink oracle of the borrowing asset. Equal to 28-borrowDecimals (10^18 * 10^18 / 10^decimals / 10^8)
}
struct MethodologySettings {
uint256 targetLeverageRatio; // Long term target ratio in precise units (10e18)
uint256 minLeverageRatio; // In precise units (10e18). If current leverage is below, rebalance target is this ratio
uint256 maxLeverageRatio; // In precise units (10e18). If current leverage is above, rebalance target is this ratio
uint256 recenteringSpeed; // % at which to rebalance back to target leverage in precise units (10e18)
uint256 rebalanceInterval; // Period of time required since last rebalance timestamp in seconds
}
struct ExecutionSettings {
uint256 unutilizedLeveragePercentage; // Percent of max borrow left unutilized in precise units (1% = 10e16)
uint256 slippageTolerance; // % in precise units to price min token receive amount from trade quantities
uint256 twapCooldownPeriod; // Cooldown period required since last trade timestamp in seconds
}
struct ExchangeSettings {
uint256 twapMaxTradeSize; // Max trade size in collateral base units
uint256 exchangeLastTradeTimestamp; // Timestamp of last trade made with this exchange
uint256 incentivizedTwapMaxTradeSize; // Max trade size for incentivized rebalances in collateral base units
bytes leverExchangeData; // Arbitrary exchange data passed into rebalance function for levering up
bytes deleverExchangeData; // Arbitrary exchange data passed into rebalance function for delevering
}
struct IncentiveSettings {
uint256 etherReward; // ETH reward for incentivized rebalances
uint256 incentivizedLeverageRatio; // Leverage ratio for incentivized rebalances
uint256 incentivizedSlippageTolerance; // Slippage tolerance percentage for incentivized rebalances
uint256 incentivizedTwapCooldownPeriod; // TWAP cooldown in seconds for incentivized rebalances
}
/* ============ Events ============ */
event Engaged(uint256 _currentLeverageRatio, uint256 _newLeverageRatio, uint256 _chunkRebalanceNotional, uint256 _totalRebalanceNotional);
event Rebalanced(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
);
event RebalanceIterated(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
);
event RipcordCalled(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _rebalanceNotional,
uint256 _etherIncentive
);
event Disengaged(uint256 _currentLeverageRatio, uint256 _newLeverageRatio, uint256 _chunkRebalanceNotional, uint256 _totalRebalanceNotional);
event MethodologySettingsUpdated(
uint256 _targetLeverageRatio,
uint256 _minLeverageRatio,
uint256 _maxLeverageRatio,
uint256 _recenteringSpeed,
uint256 _rebalanceInterval
);
event ExecutionSettingsUpdated(
uint256 _unutilizedLeveragePercentage,
uint256 _twapCooldownPeriod,
uint256 _slippageTolerance
);
event IncentiveSettingsUpdated(
uint256 _etherReward,
uint256 _incentivizedLeverageRatio,
uint256 _incentivizedSlippageTolerance,
uint256 _incentivizedTwapCooldownPeriod
);
event ExchangeUpdated(
string _exchangeName,
uint256 twapMaxTradeSize,
uint256 exchangeLastTradeTimestamp,
uint256 incentivizedTwapMaxTradeSize,
bytes leverExchangeData,
bytes deleverExchangeData
);
event ExchangeAdded(
string _exchangeName,
uint256 twapMaxTradeSize,
uint256 exchangeLastTradeTimestamp,
uint256 incentivizedTwapMaxTradeSize,
bytes leverExchangeData,
bytes deleverExchangeData
);
event ExchangeRemoved(
string _exchangeName
);
/* ============ Modifiers ============ */
/**
* Throws if rebalance is currently in TWAP`
*/
modifier noRebalanceInProgress() {
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
_;
}
/* ============ State Variables ============ */
ContractSettings internal strategy; // Struct of contracts used in the strategy (SetToken, price oracles, leverage module etc)
MethodologySettings internal methodology; // Struct containing methodology parameters
ExecutionSettings internal execution; // Struct containing execution parameters
mapping(string => ExchangeSettings) internal exchangeSettings; // Mapping from exchange name to exchange settings
IncentiveSettings internal incentive; // Struct containing incentive parameters for ripcord
string[] public enabledExchanges; // Array containing enabled exchanges
uint256 public twapLeverageRatio; // Stored leverage ratio to keep track of target between TWAP rebalances
uint256 public globalLastTradeTimestamp; // Last rebalance timestamp. Current timestamp must be greater than this variable + rebalance interval to rebalance
/* ============ Constructor ============ */
/**
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
*
* @param _manager Address of IBaseManager contract
* @param _strategy Struct of contract addresses
* @param _methodology Struct containing methodology parameters
* @param _execution Struct containing execution parameters
* @param _incentive Struct containing incentive parameters for ripcord
* @param _exchangeNames List of initial exchange names
* @param _exchangeSettings List of structs containing exchange parameters for the initial exchanges
*/
constructor(
IBaseManager _manager,
ContractSettings memory _strategy,
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive,
string[] memory _exchangeNames,
ExchangeSettings[] memory _exchangeSettings
)
public
BaseExtension(_manager)
{
strategy = _strategy;
methodology = _methodology;
execution = _execution;
incentive = _incentive;
for (uint256 i = 0; i < _exchangeNames.length; i++) {
_validateExchangeSettings(_exchangeSettings[i]);
exchangeSettings[_exchangeNames[i]] = _exchangeSettings[i];
enabledExchanges.push(_exchangeNames[i]);
}
_validateNonExchangeSettings(methodology, execution, incentive);
}
/* ============ External Functions ============ */
/**
* OPERATOR ONLY: Engage to target leverage ratio for the first time. SetToken will borrow debt position from Compound and trade for collateral asset. If target
* leverage ratio is above max borrow or max trade size, then TWAP is kicked off. To complete engage if TWAP, any valid caller must call iterateRebalance until target
* is met.
*
* @param _exchangeName the exchange used for trading
*/
function engage(string memory _exchangeName) external onlyOperator {
ActionInfo memory engageInfo = _createActionInfo();
require(engageInfo.setTotalSupply > 0, "SetToken must have > 0 supply");
require(engageInfo.collateralBalance > 0, "Collateral balance must be > 0");
require(engageInfo.borrowBalance == 0, "Debt must be 0");
LeverageInfo memory leverageInfo = LeverageInfo({
action: engageInfo,
currentLeverageRatio: PreciseUnitMath.preciseUnit(), // 1x leverage in precise units
slippageTolerance: execution.slippageTolerance,
twapMaxTradeSize: exchangeSettings[_exchangeName].twapMaxTradeSize,
exchangeName: _exchangeName
});
// Calculate total rebalance units and kick off TWAP if above max borrow or max trade size
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _calculateChunkRebalanceNotional(leverageInfo, methodology.targetLeverageRatio, true);
_lever(leverageInfo, chunkRebalanceNotional);
_updateRebalanceState(
chunkRebalanceNotional,
totalRebalanceNotional,
methodology.targetLeverageRatio,
_exchangeName
);
emit Engaged(
leverageInfo.currentLeverageRatio,
methodology.targetLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA AND ALLOWED CALLER: Rebalance according to flexible leverage methodology. If current leverage ratio is between the max and min bounds, then rebalance
* can only be called once the rebalance interval has elapsed since last timestamp. If outside the max and min, rebalance can be called anytime to bring leverage
* ratio back to the max or min bounds. The methodology will determine whether to delever or lever.
*
* Note: If the calculated current leverage ratio is above the incentivized leverage ratio or in TWAP then rebalance cannot be called. Instead, you must call
* ripcord() which is incentivized with a reward in Ether or iterateRebalance().
*
* @param _exchangeName the exchange used for trading
*/
function rebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
// use globalLastTradeTimestamps to prevent multiple rebalances being called with different exchanges during the epoch rebalance
_validateNormalRebalance(leverageInfo, methodology.rebalanceInterval, globalLastTradeTimestamp);
_validateNonTWAP();
uint256 newLeverageRatio = _calculateNewLeverageRatio(leverageInfo.currentLeverageRatio);
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _handleRebalance(leverageInfo, newLeverageRatio);
_updateRebalanceState(chunkRebalanceNotional, totalRebalanceNotional, newLeverageRatio, _exchangeName);
emit Rebalanced(
leverageInfo.currentLeverageRatio,
newLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA AND ALLOWED CALLER: Iterate a rebalance when in TWAP. TWAP cooldown period must have elapsed. If price moves advantageously, then exit without rebalancing
* and clear TWAP state. This function can only be called when below incentivized leverage ratio and in TWAP state.
*
* @param _exchangeName the exchange used for trading
*/
function iterateRebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
// Use the exchangeLastTradeTimestamp since cooldown periods are measured on a per-exchange basis, allowing it to rebalance multiple time in quick
// succession with different exchanges
_validateNormalRebalance(leverageInfo, execution.twapCooldownPeriod, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
_validateTWAP();
uint256 chunkRebalanceNotional;
uint256 totalRebalanceNotional;
if (!_isAdvantageousTWAP(leverageInfo.currentLeverageRatio)) {
(chunkRebalanceNotional, totalRebalanceNotional) = _handleRebalance(leverageInfo, twapLeverageRatio);
}
// If not advantageous, then rebalance is skipped and chunk and total rebalance notional are both 0, which means TWAP state is
// cleared
_updateIterateState(chunkRebalanceNotional, totalRebalanceNotional, _exchangeName);
emit RebalanceIterated(
leverageInfo.currentLeverageRatio,
twapLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA: In case the current leverage ratio exceeds the incentivized leverage threshold, the ripcord function can be called by anyone to return leverage ratio
* back to the max leverage ratio. This function typically would only be called during times of high downside volatility and / or normal keeper malfunctions. The caller
* of ripcord() will receive a reward in Ether. The ripcord function uses it's own TWAP cooldown period, slippage tolerance and TWAP max trade size which are typically
* looser than in regular rebalances.
*
* @param _exchangeName the exchange used for trading
*/
function ripcord(string memory _exchangeName) external onlyEOA {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
incentive.incentivizedSlippageTolerance,
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize,
_exchangeName
);
// Use the exchangeLastTradeTimestamp so it can ripcord quickly with multiple exchanges
_validateRipcord(leverageInfo, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
( uint256 chunkRebalanceNotional, ) = _calculateChunkRebalanceNotional(leverageInfo, methodology.maxLeverageRatio, false);
_delever(leverageInfo, chunkRebalanceNotional);
_updateRipcordState(_exchangeName);
uint256 etherTransferred = _transferEtherRewardToCaller(incentive.etherReward);
emit RipcordCalled(
leverageInfo.currentLeverageRatio,
methodology.maxLeverageRatio,
chunkRebalanceNotional,
etherTransferred
);
}
/**
* OPERATOR ONLY: Return leverage ratio to 1x and delever to repay loan. This can be used for upgrading or shutting down the strategy. SetToken will redeem
* collateral position and trade for debt position to repay Compound. If the chunk rebalance size is less than the total notional size, then this function will
* delever and repay entire borrow balance on Compound. If chunk rebalance size is above max borrow or max trade size, then operator must
* continue to call this function to complete repayment of loan. The function iterateRebalance will not work.
*
* Note: Delever to 0 will likely result in additional units of the borrow asset added as equity on the SetToken due to oracle price / market price mismatch
*
* @param _exchangeName the exchange used for trading
*/
function disengage(string memory _exchangeName) external onlyOperator {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
uint256 newLeverageRatio = PreciseUnitMath.preciseUnit();
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _calculateChunkRebalanceNotional(leverageInfo, newLeverageRatio, false);
if (totalRebalanceNotional > chunkRebalanceNotional) {
_delever(leverageInfo, chunkRebalanceNotional);
} else {
_deleverToZeroBorrowBalance(leverageInfo, totalRebalanceNotional);
}
emit Disengaged(
leverageInfo.currentLeverageRatio,
newLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* OPERATOR ONLY: Set methodology settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newMethodologySettings Struct containing methodology parameters
*/
function setMethodologySettings(MethodologySettings memory _newMethodologySettings) external onlyOperator noRebalanceInProgress {
methodology = _newMethodologySettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit MethodologySettingsUpdated(
methodology.targetLeverageRatio,
methodology.minLeverageRatio,
methodology.maxLeverageRatio,
methodology.recenteringSpeed,
methodology.rebalanceInterval
);
}
/**
* OPERATOR ONLY: Set execution settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newExecutionSettings Struct containing execution parameters
*/
function setExecutionSettings(ExecutionSettings memory _newExecutionSettings) external onlyOperator noRebalanceInProgress {
execution = _newExecutionSettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit ExecutionSettingsUpdated(
execution.unutilizedLeveragePercentage,
execution.twapCooldownPeriod,
execution.slippageTolerance
);
}
/**
* OPERATOR ONLY: Set incentive settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newIncentiveSettings Struct containing incentive parameters
*/
function setIncentiveSettings(IncentiveSettings memory _newIncentiveSettings) external onlyOperator noRebalanceInProgress {
incentive = _newIncentiveSettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit IncentiveSettingsUpdated(
incentive.etherReward,
incentive.incentivizedLeverageRatio,
incentive.incentivizedSlippageTolerance,
incentive.incentivizedTwapCooldownPeriod
);
}
/**
* OPERATOR ONLY: Add a new enabled exchange for trading during rebalances. New exchanges will have their exchangeLastTradeTimestamp set to 0. Adding
* exchanges during rebalances is allowed, as it is not possible to enter an unexpected state while doing so.
*
* @param _exchangeName Name of the exchange
* @param _exchangeSettings Struct containing exchange parameters
*/
function addEnabledExchange(
string memory _exchangeName,
ExchangeSettings memory _exchangeSettings
)
external
onlyOperator
{
require(exchangeSettings[_exchangeName].twapMaxTradeSize == 0, "Exchange already enabled");
_validateExchangeSettings(_exchangeSettings);
exchangeSettings[_exchangeName].twapMaxTradeSize = _exchangeSettings.twapMaxTradeSize;
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize = _exchangeSettings.incentivizedTwapMaxTradeSize;
exchangeSettings[_exchangeName].leverExchangeData = _exchangeSettings.leverExchangeData;
exchangeSettings[_exchangeName].deleverExchangeData = _exchangeSettings.deleverExchangeData;
exchangeSettings[_exchangeName].exchangeLastTradeTimestamp = 0;
enabledExchanges.push(_exchangeName);
emit ExchangeAdded(
_exchangeName,
_exchangeSettings.twapMaxTradeSize,
_exchangeSettings.exchangeLastTradeTimestamp,
_exchangeSettings.incentivizedTwapMaxTradeSize,
_exchangeSettings.leverExchangeData,
_exchangeSettings.deleverExchangeData
);
}
/**
* OPERATOR ONLY: Removes an exchange. Reverts if the exchange is not already enabled. Removing exchanges during rebalances is allowed,
* as it is not possible to enter an unexpected state while doing so.
*
* @param _exchangeName Name of exchange to remove
*/
function removeEnabledExchange(string memory _exchangeName) external onlyOperator {
require(exchangeSettings[_exchangeName].twapMaxTradeSize != 0, "Exchange not enabled");
delete exchangeSettings[_exchangeName];
enabledExchanges.removeStorage(_exchangeName);
emit ExchangeRemoved(_exchangeName);
}
/**
* OPERATOR ONLY: Updates the settings of an exchange. Reverts if exchange is not already added. When updating an exchange, exchangeLastTradeTimestamp
* is preserved. Updating exchanges during rebalances is allowed, as it is not possible to enter an unexpected state while doing so. Note: Need to
* pass in all existing parameters even if only changing a few settings.
*
* @param _exchangeName Name of the exchange
* @param _exchangeSettings Struct containing exchange parameters
*/
function updateEnabledExchange(
string memory _exchangeName,
ExchangeSettings memory _exchangeSettings
)
external
onlyOperator
{
require(exchangeSettings[_exchangeName].twapMaxTradeSize != 0, "Exchange not enabled");
_validateExchangeSettings(_exchangeSettings);
exchangeSettings[_exchangeName].twapMaxTradeSize = _exchangeSettings.twapMaxTradeSize;
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize = _exchangeSettings.incentivizedTwapMaxTradeSize;
exchangeSettings[_exchangeName].leverExchangeData = _exchangeSettings.leverExchangeData;
exchangeSettings[_exchangeName].deleverExchangeData = _exchangeSettings.deleverExchangeData;
emit ExchangeUpdated(
_exchangeName,
_exchangeSettings.twapMaxTradeSize,
_exchangeSettings.exchangeLastTradeTimestamp,
_exchangeSettings.incentivizedTwapMaxTradeSize,
_exchangeSettings.leverExchangeData,
_exchangeSettings.deleverExchangeData
);
}
/**
* OPERATOR ONLY: Withdraw entire balance of ETH in this contract to operator. Rebalance must not be in progress
*/
function withdrawEtherBalance() external onlyOperator noRebalanceInProgress {
msg.sender.transfer(address(this).balance);
}
receive() external payable {}
/* ============ External Getter Functions ============ */
/**
* Get current leverage ratio. Current leverage ratio is defined as the USD value of the collateral divided by the USD value of the SetToken. Prices for collateral
* and borrow asset are retrieved from the Compound Price Oracle.
*
* return currentLeverageRatio Current leverage ratio in precise units (10e18)
*/
function getCurrentLeverageRatio() public view returns(uint256) {
ActionInfo memory currentLeverageInfo = _createActionInfo();
return _calculateCurrentLeverageRatio(currentLeverageInfo.collateralValue, currentLeverageInfo.borrowValue);
}
/**
* Calculates the chunk rebalance size. This can be used by external contracts and keeper bots to calculate the optimal exchange to rebalance with.
* Note: this function does not take into account timestamps, so it may return a nonzero value even when shouldRebalance would return ShouldRebalance.NONE for
* all exchanges (since minimum delays have not elapsed)
*
* @param _exchangeNames Array of exchange names to get rebalance sizes for
*
* @return sizes Array of total notional chunk size. Measured in the asset that would be sold
* @return sellAsset Asset that would be sold during a rebalance
* @return buyAsset Asset that would be purchased during a rebalance
*/
function getChunkRebalanceNotional(
string[] calldata _exchangeNames
)
external
view
returns(uint256[] memory sizes, address sellAsset, address buyAsset)
{
uint256 newLeverageRatio;
uint256 currentLeverageRatio = getCurrentLeverageRatio();
bool isRipcord = false;
// if over incentivized leverage ratio, always ripcord
if (currentLeverageRatio > incentive.incentivizedLeverageRatio) {
newLeverageRatio = methodology.maxLeverageRatio;
isRipcord = true;
// if we are in an ongoing twap, use the cached twapLeverageRatio as our target leverage
} else if (twapLeverageRatio > 0) {
newLeverageRatio = twapLeverageRatio;
// if all else is false, then we would just use the normal rebalance new leverage ratio calculation
} else {
newLeverageRatio = _calculateNewLeverageRatio(currentLeverageRatio);
}
ActionInfo memory actionInfo = _createActionInfo();
bool isLever = newLeverageRatio > currentLeverageRatio;
sizes = new uint256[](_exchangeNames.length);
for (uint256 i = 0; i < _exchangeNames.length; i++) {
LeverageInfo memory leverageInfo = LeverageInfo({
action: actionInfo,
currentLeverageRatio: currentLeverageRatio,
slippageTolerance: isRipcord ? incentive.incentivizedSlippageTolerance : execution.slippageTolerance,
twapMaxTradeSize: isRipcord ?
exchangeSettings[_exchangeNames[i]].incentivizedTwapMaxTradeSize :
exchangeSettings[_exchangeNames[i]].twapMaxTradeSize,
exchangeName: _exchangeNames[i]
});
(uint256 collateralNotional, ) = _calculateChunkRebalanceNotional(leverageInfo, newLeverageRatio, isLever);
// _calculateBorrowUnits can convert both unit and notional values
sizes[i] = isLever ? _calculateBorrowUnits(collateralNotional, leverageInfo.action) : collateralNotional;
}
sellAsset = isLever ? strategy.borrowAsset : strategy.collateralAsset;
buyAsset = isLever ? strategy.collateralAsset : strategy.borrowAsset;
}
/**
* Get current Ether incentive for when current leverage ratio exceeds incentivized leverage ratio and ripcord can be called. If ETH balance on the contract is
* below the etherReward, then return the balance of ETH instead.
*
* return etherReward Quantity of ETH reward in base units (10e18)
*/
function getCurrentEtherIncentive() external view returns(uint256) {
uint256 currentLeverageRatio = getCurrentLeverageRatio();
if (currentLeverageRatio >= incentive.incentivizedLeverageRatio) {
// If ETH reward is below the balance on this contract, then return ETH balance on contract instead
return incentive.etherReward < address(this).balance ? incentive.etherReward : address(this).balance;
} else {
return 0;
}
}
/**
* Helper that checks if conditions are met for rebalance or ripcord. Returns an enum with 0 = no rebalance, 1 = call rebalance(), 2 = call iterateRebalance()
* 3 = call ripcord()
*
* @return (string[] memory, ShouldRebalance[] memory) List of exchange names and a list of enums representing whether that exchange should rebalance
*/
function shouldRebalance() external view returns(string[] memory, ShouldRebalance[] memory) {
uint256 currentLeverageRatio = getCurrentLeverageRatio();
return _shouldRebalance(currentLeverageRatio, methodology.minLeverageRatio, methodology.maxLeverageRatio);
}
/**
* Helper that checks if conditions are met for rebalance or ripcord with custom max and min bounds specified by caller. This function simplifies the
* logic for off-chain keeper bots to determine what threshold to call rebalance when leverage exceeds max or drops below min. Returns an enum with
* 0 = no rebalance, 1 = call rebalance(), 2 = call iterateRebalance()3 = call ripcord()
*
* @param _customMinLeverageRatio Min leverage ratio passed in by caller
* @param _customMaxLeverageRatio Max leverage ratio passed in by caller
*
* @return (string[] memory, ShouldRebalance[] memory) List of exchange names and a list of enums representing whether that exchange should rebalance
*/
function shouldRebalanceWithBounds(
uint256 _customMinLeverageRatio,
uint256 _customMaxLeverageRatio
)
external
view
returns(string[] memory, ShouldRebalance[] memory)
{
require (
_customMinLeverageRatio <= methodology.minLeverageRatio && _customMaxLeverageRatio >= methodology.maxLeverageRatio,
"Custom bounds must be valid"
);
uint256 currentLeverageRatio = getCurrentLeverageRatio();
return _shouldRebalance(currentLeverageRatio, _customMinLeverageRatio, _customMaxLeverageRatio);
}
/**
* Gets the list of enabled exchanges
*/
function getEnabledExchanges() external view returns (string[] memory) {
return enabledExchanges;
}
/**
* Explicit getter functions for parameter structs are defined as workaround to issues fetching structs that have dynamic types.
*/
function getStrategy() external view returns (ContractSettings memory) { return strategy; }
function getMethodology() external view returns (MethodologySettings memory) { return methodology; }
function getExecution() external view returns (ExecutionSettings memory) { return execution; }
function getIncentive() external view returns (IncentiveSettings memory) { return incentive; }
function getExchangeSettings(string memory _exchangeName) external view returns (ExchangeSettings memory) {
return exchangeSettings[_exchangeName];
}
/* ============ Internal Functions ============ */
/**
* Calculate notional rebalance quantity, whether to chunk rebalance based on max trade size and max borrow and invoke lever on CompoundLeverageModule
*
*/
function _lever(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
uint256 collateralRebalanceUnits = _chunkRebalanceNotional.preciseDiv(_leverageInfo.action.setTotalSupply);
uint256 borrowUnits = _calculateBorrowUnits(collateralRebalanceUnits, _leverageInfo.action);
uint256 minReceiveCollateralUnits = _calculateMinCollateralReceiveUnits(collateralRebalanceUnits, _leverageInfo.slippageTolerance);
bytes memory leverCallData = abi.encodeWithSignature(
"lever(address,address,address,uint256,uint256,string,bytes)",
address(strategy.setToken),
strategy.borrowAsset,
strategy.collateralAsset,
borrowUnits,
minReceiveCollateralUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].leverExchangeData
);
invokeManager(address(strategy.leverageModule), leverCallData);
}
/**
* Calculate delever units Invoke delever on CompoundLeverageModule.
*/
function _delever(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
uint256 collateralRebalanceUnits = _chunkRebalanceNotional.preciseDiv(_leverageInfo.action.setTotalSupply);
uint256 minRepayUnits = _calculateMinRepayUnits(collateralRebalanceUnits, _leverageInfo.slippageTolerance, _leverageInfo.action);
bytes memory deleverCallData = abi.encodeWithSignature(
"delever(address,address,address,uint256,uint256,string,bytes)",
address(strategy.setToken),
strategy.collateralAsset,
strategy.borrowAsset,
collateralRebalanceUnits,
minRepayUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].deleverExchangeData
);
invokeManager(address(strategy.leverageModule), deleverCallData);
}
/**
* Invoke deleverToZeroBorrowBalance on CompoundLeverageModule.
*/
function _deleverToZeroBorrowBalance(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
// Account for slippage tolerance in redeem quantity for the deleverToZeroBorrowBalance function
uint256 maxCollateralRebalanceUnits = _chunkRebalanceNotional
.preciseMul(PreciseUnitMath.preciseUnit().add(execution.slippageTolerance))
.preciseDiv(_leverageInfo.action.setTotalSupply);
bytes memory deleverToZeroBorrowBalanceCallData = abi.encodeWithSignature(
"deleverToZeroBorrowBalance(address,address,address,uint256,string,bytes)",
address(strategy.setToken),
strategy.collateralAsset,
strategy.borrowAsset,
maxCollateralRebalanceUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].deleverExchangeData
);
invokeManager(address(strategy.leverageModule), deleverToZeroBorrowBalanceCallData);
}
/**
* Check whether to delever or lever based on the current vs new leverage ratios. Used in the rebalance() and iterateRebalance() functions
*
* return uint256 Calculated notional to trade
* return uint256 Total notional to rebalance over TWAP
*/
function _handleRebalance(LeverageInfo memory _leverageInfo, uint256 _newLeverageRatio) internal returns(uint256, uint256) {
uint256 chunkRebalanceNotional;
uint256 totalRebalanceNotional;
if (_newLeverageRatio < _leverageInfo.currentLeverageRatio) {
(
chunkRebalanceNotional,
totalRebalanceNotional
) = _calculateChunkRebalanceNotional(_leverageInfo, _newLeverageRatio, false);
_delever(_leverageInfo, chunkRebalanceNotional);
} else {
(
chunkRebalanceNotional,
totalRebalanceNotional
) = _calculateChunkRebalanceNotional(_leverageInfo, _newLeverageRatio, true);
_lever(_leverageInfo, chunkRebalanceNotional);
}
return (chunkRebalanceNotional, totalRebalanceNotional);
}
/**
* Create the leverage info struct to be used in internal functions
*
* return LeverageInfo Struct containing ActionInfo and other data
*/
function _getAndValidateLeveragedInfo(uint256 _slippageTolerance, uint256 _maxTradeSize, string memory _exchangeName) internal view returns(LeverageInfo memory) {
// Assume if maxTradeSize is 0, then the exchange is not enabled. This is enforced by addEnabledExchange and updateEnabledExchange
require(_maxTradeSize > 0, "Must be valid exchange");
ActionInfo memory actionInfo = _createActionInfo();
require(actionInfo.setTotalSupply > 0, "SetToken must have > 0 supply");
require(actionInfo.collateralBalance > 0, "Collateral balance must be > 0");
require(actionInfo.borrowBalance > 0, "Borrow balance must exist");
// Get current leverage ratio
uint256 currentLeverageRatio = _calculateCurrentLeverageRatio(
actionInfo.collateralValue,
actionInfo.borrowValue
);
return LeverageInfo({
action: actionInfo,
currentLeverageRatio: currentLeverageRatio,
slippageTolerance: _slippageTolerance,
twapMaxTradeSize: _maxTradeSize,
exchangeName: _exchangeName
});
}
/**
* Create the action info struct to be used in internal functions
*
* return ActionInfo Struct containing data used by internal lever and delever functions
*/
function _createActionInfo() internal view returns(ActionInfo memory) {
ActionInfo memory rebalanceInfo;
// Calculate prices from chainlink. Adjusts decimals to be in line with Compound's oracles. Chainlink returns prices with 8 decimal places, but
// compound expects 36 - underlyingDecimals decimal places from their oracles. This is so that when the underlying amount is multiplied by the
// received price, the collateral valuation is normalized to 36 decimals. To perform this adjustment, we multiply by 10^(36 - 8 - underlyingDeciamls)
int256 rawCollateralPrice = strategy.collateralPriceOracle.latestAnswer();
rebalanceInfo.collateralPrice = rawCollateralPrice.toUint256().mul(10 ** strategy.collateralDecimalAdjustment);
int256 rawBorrowPrice = strategy.borrowPriceOracle.latestAnswer();
rebalanceInfo.borrowPrice = rawBorrowPrice.toUint256().mul(10 ** strategy.borrowDecimalAdjustment);
// Calculate stored exchange rate which does not trigger a state update
uint256 cTokenBalance = strategy.targetCollateralCToken.balanceOf(address(strategy.setToken));
rebalanceInfo.collateralBalance = cTokenBalance.preciseMul(strategy.targetCollateralCToken.exchangeRateStored());
rebalanceInfo.borrowBalance = strategy.targetBorrowCToken.borrowBalanceStored(address(strategy.setToken));
rebalanceInfo.collateralValue = rebalanceInfo.collateralPrice.preciseMul(rebalanceInfo.collateralBalance);
rebalanceInfo.borrowValue = rebalanceInfo.borrowPrice.preciseMul(rebalanceInfo.borrowBalance);
rebalanceInfo.setTotalSupply = strategy.setToken.totalSupply();
return rebalanceInfo;
}
/**
* Validate non-exchange settings in constructor and setters when updating.
*/
function _validateNonExchangeSettings(
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive
)
internal
pure
{
require (
_methodology.minLeverageRatio <= _methodology.targetLeverageRatio && _methodology.minLeverageRatio > 0,
"Must be valid min leverage"
);
require (
_methodology.maxLeverageRatio >= _methodology.targetLeverageRatio,
"Must be valid max leverage"
);
require (
_methodology.recenteringSpeed <= PreciseUnitMath.preciseUnit() && _methodology.recenteringSpeed > 0,
"Must be valid recentering speed"
);
require (
_execution.unutilizedLeveragePercentage <= PreciseUnitMath.preciseUnit(),
"Unutilized leverage must be <100%"
);
require (
_execution.slippageTolerance <= PreciseUnitMath.preciseUnit(),
"Slippage tolerance must be <100%"
);
require (
_incentive.incentivizedSlippageTolerance <= PreciseUnitMath.preciseUnit(),
"Incentivized slippage tolerance must be <100%"
);
require (
_incentive.incentivizedLeverageRatio >= _methodology.maxLeverageRatio,
"Incentivized leverage ratio must be > max leverage ratio"
);
require (
_methodology.rebalanceInterval >= _execution.twapCooldownPeriod,
"Rebalance interval must be greater than TWAP cooldown period"
);
require (
_execution.twapCooldownPeriod >= _incentive.incentivizedTwapCooldownPeriod,
"TWAP cooldown must be greater than incentivized TWAP cooldown"
);
}
/**
* Validate an ExchangeSettings struct when adding or updating an exchange. Does not validate that twapMaxTradeSize < incentivizedMaxTradeSize since
* it may be useful to disable exchanges for ripcord by setting incentivizedMaxTradeSize to 0.
*/
function _validateExchangeSettings(ExchangeSettings memory _settings) internal pure {
require(_settings.twapMaxTradeSize != 0, "Max TWAP trade size must not be 0");
}
/**
* Validate that current leverage is below incentivized leverage ratio and cooldown / rebalance period has elapsed or outsize max/min bounds. Used
* in rebalance() and iterateRebalance() functions
*/
function _validateNormalRebalance(LeverageInfo memory _leverageInfo, uint256 _coolDown, uint256 _lastTradeTimestamp) internal view {
require(_leverageInfo.currentLeverageRatio < incentive.incentivizedLeverageRatio, "Must be below incentivized leverage ratio");
require(
block.timestamp.sub(_lastTradeTimestamp) > _coolDown
|| _leverageInfo.currentLeverageRatio > methodology.maxLeverageRatio
|| _leverageInfo.currentLeverageRatio < methodology.minLeverageRatio,
"Cooldown not elapsed or not valid leverage ratio"
);
}
/**
* Validate that current leverage is above incentivized leverage ratio and incentivized cooldown period has elapsed in ripcord()
*/
function _validateRipcord(LeverageInfo memory _leverageInfo, uint256 _lastTradeTimestamp) internal view {
require(_leverageInfo.currentLeverageRatio >= incentive.incentivizedLeverageRatio, "Must be above incentivized leverage ratio");
// If currently in the midst of a TWAP rebalance, ensure that the cooldown period has elapsed
require(_lastTradeTimestamp.add(incentive.incentivizedTwapCooldownPeriod) < block.timestamp, "TWAP cooldown must have elapsed");
}
/**
* Validate TWAP in the iterateRebalance() function
*/
function _validateTWAP() internal view {
require(twapLeverageRatio > 0, "Not in TWAP state");
}
/**
* Validate not TWAP in the rebalance() function
*/
function _validateNonTWAP() internal view {
require(twapLeverageRatio == 0, "Must call iterate");
}
/**
* Check if price has moved advantageously while in the midst of the TWAP rebalance. This means the current leverage ratio has moved over/under
* the stored TWAP leverage ratio on lever/delever so there is no need to execute a rebalance. Used in iterateRebalance()
*/
function _isAdvantageousTWAP(uint256 _currentLeverageRatio) internal view returns (bool) {
return (
(twapLeverageRa...
// [truncated — 62201 bytes total]
GIMExtension.sol 319 lines
/*
Copyright 2021 IndexCooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IGeneralIndexModule } from "../interfaces/IGeneralIndexModule.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title GIMExtension
* @author Set Protocol
*
* Smart contract manager extension that acts as a pass-through contract for interacting with GeneralIndexModule.
* All functions are only callable by operator. startRebalance() on GIM maps to startRebalanceWithUnits on
* GIMExtension.
*/
contract GIMExtension is BaseExtension {
using AddressArrayUtils for address[];
using SafeMath for uint256;
/* ============ State Variables ============ */
ISetToken public setToken;
IGeneralIndexModule public generalIndexModule; // GIM
/* ============ Constructor ============ */
constructor(IBaseManager _manager, IGeneralIndexModule _generalIndexModule) public BaseExtension(_manager) {
generalIndexModule = _generalIndexModule;
setToken = manager.setToken();
}
/* ============ External Functions ============ */
/**
* ONLY OPERATOR: Submits a startRebalance call to GeneralIndexModule. Uses internal function so that this contract can be inherited and
* custom startRebalance logic can be added on top. Components array is sorted in new and old components arrays in order to conform to
* startRebalance interface. See GIM for function specific restrictions.
* @param _components Array of components involved in rebalance inclusive of components being removed from set (targetUnit = 0)
* @param _targetUnits Array of target units at end of rebalance, maps to same index of _components array
* @param _positionMultiplier Position multiplier when target units were calculated, needed in order to adjust target units if fees accrued
*/
function startRebalanceWithUnits(
address[] calldata _components,
uint256[] calldata _targetUnits,
uint256 _positionMultiplier
)
external
onlyOperator
{
(
address[] memory newComponents,
uint256[] memory newComponentsTargetUnits,
uint256[] memory oldComponentsTargetUnits
) = _sortNewAndOldComponents(_components, _targetUnits);
_startRebalance(newComponents, newComponentsTargetUnits, oldComponentsTargetUnits, _positionMultiplier);
}
/**
* ONLY OPERATOR: Submits a setTradeMaximums call to GeneralIndexModule. See GIM for function specific restrictions.
*
* @param _components Array of components
* @param _tradeMaximums Array of trade maximums mapping to correct component
*/
function setTradeMaximums(
address[] memory _components,
uint256[] memory _tradeMaximums
)
external
onlyOperator
{
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.setTradeMaximums.selector,
setToken,
_components,
_tradeMaximums
);
invokeManager(address(generalIndexModule), callData);
}
/**
* ONLY OPERATOR: Submits a setExchanges call to GeneralIndexModule. See GIM for function specific restrictions.
*
* @param _components Array of components
* @param _exchangeNames Array of exchange names mapping to correct component
*/
function setExchanges(
address[] memory _components,
string[] memory _exchangeNames
)
external
onlyOperator
{
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.setExchanges.selector,
setToken,
_components,
_exchangeNames
);
invokeManager(address(generalIndexModule), callData);
}
/**
* ONLY OPERATOR: Submits a setCoolOffPeriods call to GeneralIndexModule. See GIM for function specific restrictions.
*
* @param _components Array of components
* @param _coolOffPeriods Array of cool off periods to correct component
*/
function setCoolOffPeriods(
address[] memory _components,
uint256[] memory _coolOffPeriods
)
external
onlyOperator
{
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.setCoolOffPeriods.selector,
setToken,
_components,
_coolOffPeriods
);
invokeManager(address(generalIndexModule), callData);
}
/**
* ONLY OPERATOR: Submits a setExchangeData call to GeneralIndexModule. See GIM for function specific restrictions.
*
* @param _components Array of components
* @param _exchangeData Array of exchange specific arbitrary bytes data
*/
function setExchangeData(
address[] memory _components,
bytes[] memory _exchangeData
)
external
onlyOperator
{
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.setExchangeData.selector,
setToken,
_components,
_exchangeData
);
invokeManager(address(generalIndexModule), callData);
}
/**
* ONLY OPERATOR: Submits a setRaiseTargetPercentage call to GeneralIndexModule. See GIM for function specific restrictions.
*
* @param _raiseTargetPercentage Amount to raise all component's unit targets by (in precise units)
*/
function setRaiseTargetPercentage(uint256 _raiseTargetPercentage) external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.setRaiseTargetPercentage.selector,
setToken,
_raiseTargetPercentage
);
invokeManager(address(generalIndexModule), callData);
}
/**
* ONLY OPERATOR: Submits a setTraderStatus call to GeneralIndexModule. See GIM for function specific restrictions.
*
* @param _traders Array trader addresses to toggle status
* @param _statuses Booleans indicating if matching trader can trade
*/
function setTraderStatus(
address[] memory _traders,
bool[] memory _statuses
)
external
onlyOperator
{
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.setTraderStatus.selector,
setToken,
_traders,
_statuses
);
invokeManager(address(generalIndexModule), callData);
}
/**
* ONLY OPERATOR: Submits a setAnyoneTrade call to GeneralIndexModule. See GIM for function specific restrictions.
*
* @param _status Boolean indicating if anyone can call trade
*/
function setAnyoneTrade(bool _status) external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.setAnyoneTrade.selector,
setToken,
_status
);
invokeManager(address(generalIndexModule), callData);
}
/**
* ONLY OPERATOR: Submits a initialize call to GeneralIndexModule. See GIM for function specific restrictions.
*/
function initialize() external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.initialize.selector,
setToken
);
invokeManager(address(generalIndexModule), callData);
}
/* ============ Internal Functions ============ */
/**
* Internal function that creates calldata and submits startRebalance call to GeneralIndexModule.
*
* @param _newComponents Array of new components to add to allocation
* @param _newComponentsTargetUnits Array of target units at end of rebalance for new components, maps to same index of _newComponents array
* @param _oldComponentsTargetUnits Array of target units at end of rebalance for old component, maps to same index of
* _setToken.getComponents() array, if component being removed set to 0.
* @param _positionMultiplier Position multiplier when target units were calculated, needed in order to adjust target units
* if fees accrued
*/
function _startRebalance(
address[] memory _newComponents,
uint256[] memory _newComponentsTargetUnits,
uint256[] memory _oldComponentsTargetUnits,
uint256 _positionMultiplier
)
internal
{
bytes memory callData = abi.encodeWithSelector(
IGeneralIndexModule.startRebalance.selector,
setToken,
_newComponents,
_newComponentsTargetUnits,
_oldComponentsTargetUnits,
_positionMultiplier
);
invokeManager(address(generalIndexModule), callData);
}
/**
* Internal function that sorts components into old and new components and builds the requisite target unit arrays. Old components target units
* MUST maintain the order of the components array on the SetToken. The _components array MUST contain an entry for all current components even if
* component is being removed (targetUnit = 0). This is validated implicitly by calculating the amount of new components that would be added as
* implied by the array lengths, if more than the expected amount of new components are added then it implies an old component is missing.
*
* @param _components Array of components involved in rebalance inclusive of components being removed from set (targetUnit = 0)
* @param _targetUnits Array of target units at end of rebalance, maps to same index of _components array
*/
function _sortNewAndOldComponents(
address[] memory _components,
uint256[] memory _targetUnits
)
internal
view
returns (address[] memory, uint256[] memory, uint256[] memory)
{
address[] memory currentComponents = setToken.getComponents();
uint256 currentSetComponentsLength = currentComponents.length;
uint256 rebalanceComponentsLength = _components.length;
require(rebalanceComponentsLength >= currentSetComponentsLength, "Components array must be equal or longer than current components");
// We assume that there is an entry for each old component regardless of if it's 0, so any additional components in the array
// must be added as a new component. Hence we can declare the length of the new components array as the difference between
// rebalanceComponentsLength and currentSetComponentsLength
uint256[] memory oldComponentsTargetUnits = new uint256[](currentSetComponentsLength);
address[] memory newComponents = new address[](rebalanceComponentsLength.sub(currentSetComponentsLength));
uint256[] memory newTargetUnits = new uint256[](rebalanceComponentsLength.sub(currentSetComponentsLength));
uint256 newCounter; // Count amount of components added to newComponents array to add new components to next index
for (uint256 i = 0; i < rebalanceComponentsLength; i++) {
address component = _components[i];
(uint256 index, bool isIn) = currentComponents.indexOf(component);
if (isIn) {
oldComponentsTargetUnits[index] = _targetUnits[i]; // Use index in order to map to correct component in currentComponents array
} else {
require(newCounter < newComponents.length, "Unexpected new component added");
newComponents[newCounter] = component;
newTargetUnits[newCounter] = _targetUnits[i];
newCounter = newCounter.add(1);
}
}
return (newComponents, newTargetUnits, oldComponentsTargetUnits);
}
}
GovernanceExtension.sol 171 lines
/*
Copyright 2021 IndexCooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IGovernanceModule } from "../interfaces/IGovernanceModule.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title GovernanceExtension
* @author Set Protocol
*
* Smart contract extension that acts as a manager interface for interacting with the Set Protocol
* GovernanceModule to perform meta-governance actions. All governance functions are callable only
* by a subset of allowed callers. The operator has the power to add/remove callers from the allowed
* callers mapping.
*/
contract GovernanceExtension is BaseExtension {
/* ============ State Variables ============ */
ISetToken public setToken;
IGovernanceModule public governanceModule;
/* ============ Constructor ============ */
constructor(IBaseManager _manager, IGovernanceModule _governanceModule) public BaseExtension(_manager) {
governanceModule = _governanceModule;
setToken = manager.setToken();
}
/* ============ External Functions ============ */
/**
* ONLY APPROVED CALLER: Submits a delegate call to the GovernanceModule. Approved caller mapping
* is part of BaseExtension.
*
* @param _governanceName Name of governance extension being used
*/
function delegate(
string memory _governanceName,
address _delegatee
)
external
onlyAllowedCaller(msg.sender)
{
bytes memory callData = abi.encodeWithSelector(
IGovernanceModule.delegate.selector,
setToken,
_governanceName,
_delegatee
);
invokeManager(address(governanceModule), callData);
}
/**
* ONLY APPROVED CALLER: Submits a proposal call to the GovernanceModule. Approved caller mapping
* is part of BaseExtension.
*
* @param _governanceName Name of governance extension being used
* @param _proposalData Byte data of proposal
*/
function propose(
string memory _governanceName,
bytes memory _proposalData
)
external
onlyAllowedCaller(msg.sender)
{
bytes memory callData = abi.encodeWithSelector(
IGovernanceModule.propose.selector,
setToken,
_governanceName,
_proposalData
);
invokeManager(address(governanceModule), callData);
}
/**
* ONLY APPROVED CALLER: Submits a register call to the GovernanceModule. Approved caller mapping
* is part of BaseExtension.
*
* @param _governanceName Name of governance extension being used
*/
function register(string memory _governanceName) external onlyAllowedCaller(msg.sender) {
bytes memory callData = abi.encodeWithSelector(
IGovernanceModule.register.selector,
setToken,
_governanceName
);
invokeManager(address(governanceModule), callData);
}
/**
* ONLY APPROVED CALLER: Submits a revoke call to the GovernanceModule. Approved caller mapping
* is part of BaseExtension.
*
* @param _governanceName Name of governance extension being used
*/
function revoke(string memory _governanceName) external onlyAllowedCaller(msg.sender) {
bytes memory callData = abi.encodeWithSelector(
IGovernanceModule.revoke.selector,
setToken,
_governanceName
);
invokeManager(address(governanceModule), callData);
}
/**
* ONLY APPROVED CALLER: Submits a vote call to the GovernanceModule. Approved caller mapping
* is part of BaseExtension.
*
* @param _governanceName Name of governance extension being used
* @param _proposalId Id of proposal being voted on
* @param _support Boolean indicating if supporting proposal
* @param _data Arbitrary bytes to be used to construct vote call data
*/
function vote(
string memory _governanceName,
uint256 _proposalId,
bool _support,
bytes memory _data
)
external
onlyAllowedCaller(msg.sender)
{
bytes memory callData = abi.encodeWithSelector(
IGovernanceModule.vote.selector,
setToken,
_governanceName,
_proposalId,
_support,
_data
);
invokeManager(address(governanceModule), callData);
}
/**
* ONLY OPERATOR: Initialize GovernanceModule for Set
*/
function initialize() external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
IGovernanceModule.initialize.selector,
setToken
);
invokeManager(address(governanceModule), callData);
}
}
MigrationExtension.sol 744 lines
/*
Copyright 2024 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IBalancerVault } from "../interfaces/IBalancerVault.sol";
import { IMorpho } from "../interfaces/IMorpho.sol";
import { FlashLoanSimpleReceiverBase } from "../lib/FlashLoanSimpleReceiverBase.sol";
import { IPoolAddressesProvider } from "../interfaces/IPoolAddressesProvider.sol";
import { INonfungiblePositionManager } from "../interfaces/external/uniswap-v3/INonfungiblePositionManager.sol";
import { IUniswapV3Pool } from "../interfaces/external/uniswap-v3/IUniswapV3Pool.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ITradeModule } from "../interfaces/ITradeModule.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title MigrationExtension
* @author Index Coop
* @notice This extension facilitates the migration of a SetToken's position from an unwrapped collateral
* asset to another SetToken that consists solely of Aave's wrapped collateral asset. The migration is
* executed through several steps: obtaining a flash loan of the unwrapped collateral, minting the required
* quantity of the wrapped SetToken, adding liquidity to the Uniswap V3 pool, swapping the unwrapped
* collateral for the wrapped SetToken, removing liquidity from the Uniswap V3 pool, and finally,
* redeeming any excess wrapped SetToken. This process is specifically designed to efficiently migrate
* the SetToken's collateral using only the TradeModule on the SetToken.
*/
contract MigrationExtension is BaseExtension, FlashLoanSimpleReceiverBase, IERC721Receiver {
using PreciseUnitMath for uint256;
using SafeCast for int256;
using SafeERC20 for IERC20;
using SafeMath for uint256;
/* ============ Structs ============ */
struct DecodedParams {
uint256 supplyLiquidityAmount0Desired;
uint256 supplyLiquidityAmount1Desired;
uint256 supplyLiquidityAmount0Min;
uint256 supplyLiquidityAmount1Min;
uint256 tokenId;
string exchangeName;
uint256 underlyingTradeUnits;
uint256 wrappedSetTokenTradeUnits;
bytes exchangeData;
uint256 redeemLiquidityAmount0Min;
uint256 redeemLiquidityAmount1Min;
bool isUnderlyingToken0;
}
/* ========== State Variables ========= */
ISetToken public immutable setToken;
IERC20 public immutable underlyingToken;
IERC20 public immutable aaveToken;
ISetToken public immutable wrappedSetToken;
ITradeModule public immutable tradeModule;
IDebtIssuanceModule public immutable issuanceModule;
INonfungiblePositionManager public immutable nonfungiblePositionManager;
IMorpho public immutable morpho;
IBalancerVault public immutable balancer;
uint256[] public tokenIds; // UniV3 LP Token IDs
/* ============ Constructor ============ */
/**
* @notice Initializes the MigrationExtension with immutable migration variables.
* @param _manager BaseManager contract for managing the SetToken's operations and permissions.
* @param _underlyingToken Address of the underlying token to be migrated.
* @param _aaveToken Address of Aave's wrapped collateral asset.
* @param _wrappedSetToken SetToken that consists solely of Aave's wrapped collateral asset.
* @param _tradeModule TradeModule address for executing trades on behalf of the SetToken.
* @param _issuanceModule IssuanceModule address for managing issuance and redemption of the Wrapped SetToken.
* @param _nonfungiblePositionManager Uniswap V3's NonFungiblePositionManager for managing liquidity positions.
* @param _addressProvider Aave V3's Pool Address Provider, used for accessing the Aave lending pool.
*/
constructor(
IBaseManager _manager,
IERC20 _underlyingToken,
IERC20 _aaveToken,
ISetToken _wrappedSetToken,
ITradeModule _tradeModule,
IDebtIssuanceModule _issuanceModule,
INonfungiblePositionManager _nonfungiblePositionManager,
IPoolAddressesProvider _addressProvider,
IMorpho _morpho,
IBalancerVault _balancer
)
public
BaseExtension(_manager)
FlashLoanSimpleReceiverBase(_addressProvider)
{
manager = _manager;
setToken = manager.setToken();
underlyingToken = _underlyingToken;
aaveToken = _aaveToken;
wrappedSetToken = _wrappedSetToken;
tradeModule = _tradeModule;
issuanceModule = _issuanceModule;
nonfungiblePositionManager = _nonfungiblePositionManager;
morpho = _morpho;
balancer = _balancer;
}
/* ========== External Functions ========== */
/**
* @notice OPERATOR ONLY: Initializes the Set Token on the Trade Module.
*/
function initialize() external onlyOperator {
bytes memory data = abi.encodeWithSelector(tradeModule.initialize.selector, setToken);
invokeManager(address(tradeModule), data);
}
/**
* @notice OPERATOR ONLY: Executes a trade on a supported DEX.
* @dev Although the SetToken units are passed in for the send and receive quantities, the total quantity
* sent and received is the quantity of SetToken units multiplied by the SetToken totalSupply.
* @param _exchangeName The human-readable name of the exchange in the integrations registry.
* @param _sendToken The address of the token being sent to the exchange.
* @param _sendQuantity The amount of the token (in SetToken units) being sent to the exchange.
* @param _receiveToken The address of the token being received from the exchange.
* @param _minReceiveQuantity The minimum amount of the receive token (in SetToken units) expected from the exchange.
* @param _data Arbitrary data used to construct the trade call data.
*/
function trade(
string memory _exchangeName,
address _sendToken,
uint256 _sendQuantity,
address _receiveToken,
uint256 _minReceiveQuantity,
bytes memory _data
)
external
onlyOperator
{
_trade(
_exchangeName,
_sendToken,
_sendQuantity,
_receiveToken,
_minReceiveQuantity,
_data
);
}
/**
* @notice OPERATOR ONLY: Mints a new liquidity position in the Uniswap V3 pool.
* @param _amount0Desired The desired amount of token0 to be added as liquidity.
* @param _amount1Desired The desired amount of token1 to be added as liquidity.
* @param _amount0Min The minimum amount of token0 to be added as liquidity.
* @param _amount1Min The minimum amount of token1 to be added as liquidity.
* @param _tickLower The lower end of the desired tick range for the position.
* @param _tickUpper The upper end of the desired tick range for the position.
* @param _fee The fee tier of the Uniswap V3 pool in which to add liquidity.
* @param _isUnderlyingToken0 True if the underlying token is token0, false if it is token1.
*/
function mintLiquidityPosition(
uint256 _amount0Desired,
uint256 _amount1Desired,
uint256 _amount0Min,
uint256 _amount1Min,
int24 _tickLower,
int24 _tickUpper,
uint24 _fee,
bool _isUnderlyingToken0
)
external
onlyOperator
{
_mintLiquidityPosition(
_amount0Desired,
_amount1Desired,
_amount0Min,
_amount1Min,
_tickLower,
_tickUpper,
_fee,
_isUnderlyingToken0
);
}
/**
* @notice OPERATOR ONLY: Increases liquidity position in the Uniswap V3 pool.
* @param _amount0Desired The desired amount of token0 to be added as liquidity.
* @param _amount1Desired The desired amount of token1 to be added as liquidity.
* @param _amount0Min The minimum amount of token0 to be added as liquidity.
* @param _amount1Min The minimum amount of token1 to be added as liquidity.
* @param _tokenId The ID of the UniV3 LP Token for which liquidity is being increased.
* @param _isUnderlyingToken0 True if the underlying token is token0, false if it is token1.
* @return liquidity The new liquidity amount as a result of the increase.
*/
function increaseLiquidityPosition(
uint256 _amount0Desired,
uint256 _amount1Desired,
uint256 _amount0Min,
uint256 _amount1Min,
uint256 _tokenId,
bool _isUnderlyingToken0
)
external
onlyOperator
returns (uint128 liquidity)
{
liquidity = _increaseLiquidityPosition(
_amount0Desired,
_amount1Desired,
_amount0Min,
_amount1Min,
_tokenId,
_isUnderlyingToken0
);
}
/**
* @notice OPERATOR ONLY: Decreases and collects from a liquidity position in the Uniswap V3 pool.
* @param _tokenId The ID of the UniV3 LP Token for which liquidity is being decreased.
* @param _liquidity The amount of liquidity to decrease.
* @param _amount0Min The minimum amount of token0 that should be accounted for the burned liquidity.
* @param _amount1Min The minimum amount of token1 that should be accounted for the burned liquidity.
*/
function decreaseLiquidityPosition(
uint256 _tokenId,
uint128 _liquidity,
uint256 _amount0Min,
uint256 _amount1Min
)
external
onlyOperator
{
_decreaseLiquidityPosition(
_tokenId,
_liquidity,
_amount0Min,
_amount1Min
);
}
/**
* @notice OPERATOR ONLY: Migrates a SetToken's position from an unwrapped collateral asset to another SetToken
* that consists solely of Aave's wrapped collateral asset
* using Aave Flashloan
* @param _decodedParams The decoded migration parameters.
* @param _underlyingLoanAmount The amount of unwrapped collateral asset to be borrowed via flash loan.
* @param _maxSubsidy The maximum amount of unwrapped collateral asset to be transferred to the Extension as a subsidy.
* @return underlyingOutputAmount The amount of unwrapped collateral asset returned to the operator.
*/
function migrateAave(
DecodedParams memory _decodedParams,
uint256 _underlyingLoanAmount,
uint256 _maxSubsidy
)
external
onlyOperator
returns (uint256 underlyingOutputAmount)
{
// Subsidize the migration
if (_maxSubsidy > 0) {
underlyingToken.transferFrom(msg.sender, address(this), _maxSubsidy);
}
// Encode migration parameters for flash loan callback
bytes memory params = abi.encode(_decodedParams);
// Request flash loan for the underlying token
POOL.flashLoanSimple(
address(this),
address(underlyingToken),
_underlyingLoanAmount,
params,
0
);
// Return remaining underlying token to the operator
underlyingOutputAmount = _returnExcessUnderlying();
}
/**
* @notice OPERATOR ONLY: Migrates a SetToken's position from an unwrapped collateral asset to another SetToken
* that consists solely of Aave's wrapped collateral asset
* using Balancer Flashloan
* @param _decodedParams The decoded migration parameters.
* @param _underlyingLoanAmount The amount of unwrapped collateral asset to be borrowed via flash loan.
* @param _maxSubsidy The maximum amount of unwrapped collateral asset to be transferred to the Extension as a subsidy.
* @return underlyingOutputAmount The amount of unwrapped collateral asset returned to the operator.
*/
function migrateBalancer(
DecodedParams memory _decodedParams,
uint256 _underlyingLoanAmount,
uint256 _maxSubsidy
)
external
onlyOperator
returns (uint256 underlyingOutputAmount)
{
// Subsidize the migration
if (_maxSubsidy > 0) {
underlyingToken.transferFrom(msg.sender, address(this), _maxSubsidy);
}
// Encode migration parameters for flash loan callback
bytes memory params = abi.encode(_decodedParams);
address[] memory tokens = new address[](1);
tokens[0] = address(underlyingToken);
uint256[] memory amounts = new uint256[](1);
amounts[0] = _underlyingLoanAmount;
// Request flash loan for the underlying token
balancer.flashLoan(address(this), tokens, amounts, params);
// Return remaining underlying token to the operator
underlyingOutputAmount = _returnExcessUnderlying();
}
/**
* @dev Callback function for Balancer flashloan
*/
function receiveFlashLoan(
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory params
) external {
require(msg.sender == address(balancer));
// Decode parameters and migrate
DecodedParams memory decodedParams = abi.decode(params, (DecodedParams));
_migrate(decodedParams);
underlyingToken.transfer(address(balancer), amounts[0] + feeAmounts[0]);
}
/**
* @notice OPERATOR ONLY: Migrates a SetToken's position from an unwrapped collateral asset to another SetToken
* that consists solely of Aave's wrapped collateral asset
* using Morpho Flashloan
* @param _decodedParams The decoded migration parameters.
* @param _underlyingLoanAmount The amount of unwrapped collateral asset to be borrowed via flash loan.
* @param _maxSubsidy The maximum amount of unwrapped collateral asset to be transferred to the Extension as a subsidy.
* @return underlyingOutputAmount The amount of unwrapped collateral asset returned to the operator.
*/
function migrateMorpho(
DecodedParams memory _decodedParams,
uint256 _underlyingLoanAmount,
uint256 _maxSubsidy
)
external
onlyOperator
returns (uint256 underlyingOutputAmount)
{
// Subsidize the migration
if (_maxSubsidy > 0) {
underlyingToken.transferFrom(msg.sender, address(this), _maxSubsidy);
}
// Encode migration parameters for flash loan callback
bytes memory params = abi.encode(_decodedParams);
// Request flash loan for the underlying token
morpho.flashLoan(address(underlyingToken), _underlyingLoanAmount, params);
// Return remaining underlying token to the operator
underlyingOutputAmount = _returnExcessUnderlying();
}
/**
* @dev Callback function for Morpho Flashloan
*/
function onMorphoFlashLoan(uint256 assets, bytes calldata params) external
{
require(msg.sender == address(morpho), "MigrationExtension: invalid flashloan sender");
// Decode parameters and migrate
DecodedParams memory decodedParams = abi.decode(params, (DecodedParams));
_migrate(decodedParams);
underlyingToken.approve(address(morpho), assets);
}
/**
* @dev Callback function for Aave V3 flash loan, executed post-loan. It decodes the provided parameters, conducts the migration, and repays the flash loan.
* @param amount The amount borrowed.
* @param premium The additional fee charged for the flash loan.
* @param initiator The initiator of the flash loan.
* @param params Encoded migration parameters.
* @return True if the operation is successful.
*/
function executeOperation(
address, // asset
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
)
external
override
returns (bool)
{
require(msg.sender == address(POOL), "MigrationExtension: invalid flashloan sender");
require(initiator == address(this), "MigrationExtension: invalid flashloan initiator");
// Decode parameters and migrate
DecodedParams memory decodedParams = abi.decode(params, (DecodedParams));
_migrate(decodedParams);
underlyingToken.approve(address(POOL), amount + premium);
return true;
}
/**
* @notice Receives ERC721 tokens, required for Uniswap V3 LP NFT handling.
* @dev Callback function for ERC721 token transfers, enabling the contract to receive Uniswap V3 LP NFTs. Always returns the selector to indicate successful receipt.
* @return The selector of the `onERC721Received` function.
*/
function onERC721Received(
address, // operator
address, // from
uint256, // tokenId
bytes calldata // data
)
external
override
returns (bytes4)
{
return this.onERC721Received.selector;
}
/**
* @notice OPERATOR ONLY: Transfers any residual balances to the operator's address.
* @dev This function is intended to recover tokens that might have been left behind
* due to the migration process or any other operation. It ensures that the contract
* does not retain any assets inadvertently. Only callable by the operator.
* @param _token The address of the token to be swept.
*/
function sweepTokens(address _token) external onlyOperator {
IERC20 token = IERC20(_token);
uint256 balance = token.balanceOf(address(this));
require(balance > 0, "MigrationExtension: no balance to sweep");
token.transfer(manager.operator(), balance);
}
/* ========== Internal Functions ========== */
/**
* @dev Conducts the actual migration steps utilizing the decoded parameters from the flash loan callback.
* @param decodedParams The decoded set of parameters needed for migration.
*/
function _migrate(DecodedParams memory decodedParams) internal {
uint256 wrappedSetTokenSupplyLiquidityAmount = decodedParams.isUnderlyingToken0
? decodedParams.supplyLiquidityAmount1Desired
: decodedParams.supplyLiquidityAmount0Desired;
_issueRequiredWrappedSetToken(wrappedSetTokenSupplyLiquidityAmount);
uint128 liquidity = _increaseLiquidityPosition(
decodedParams.supplyLiquidityAmount0Desired,
decodedParams.supplyLiquidityAmount1Desired,
decodedParams.supplyLiquidityAmount0Min,
decodedParams.supplyLiquidityAmount1Min,
decodedParams.tokenId,
decodedParams.isUnderlyingToken0
);
_trade(
decodedParams.exchangeName,
address(underlyingToken),
decodedParams.underlyingTradeUnits,
address(wrappedSetToken),
decodedParams.wrappedSetTokenTradeUnits,
decodedParams.exchangeData
);
_decreaseLiquidityPosition(
decodedParams.tokenId,
liquidity,
decodedParams.redeemLiquidityAmount0Min,
decodedParams.redeemLiquidityAmount1Min
);
_redeemExcessWrappedSetToken();
}
/**
* @dev Internal function to execute trades. This function constructs the trade call data and invokes the trade module
* to execute the trade. The SetToken units for send and receive quantities are automatically scaled up by the SetToken's
* total supply.
* @param _exchangeName The human-readable name of the exchange in the integrations registry.
* @param _sendToken The address of the token being sent to the exchange.
* @param _sendQuantity The amount of the token (in SetToken units) being sent to the exchange.
* @param _receiveToken The address of the token being received from the exchange.
* @param _minReceiveQuantity The minimum amount of the receive token (in SetToken units) expected from the exchange.
* @param _data Arbitrary data used to construct the trade call data.
*/
function _trade(
string memory _exchangeName,
address _sendToken,
uint256 _sendQuantity,
address _receiveToken,
uint256 _minReceiveQuantity,
bytes memory _data
)
internal
{
bytes memory callData = abi.encodeWithSignature(
"trade(address,string,address,uint256,address,uint256,bytes)",
setToken,
_exchangeName,
_sendToken,
_sendQuantity,
_receiveToken,
_minReceiveQuantity,
_data
);
invokeManager(address(tradeModule), callData);
}
/**
* @dev Issues the required amount of wrapped SetToken for the liquidity increase
* @param _wrappedSetTokenSupplyLiquidityAmount The amount of wrapped SetToken to be supplied to the pool.
*/
function _issueRequiredWrappedSetToken(uint256 _wrappedSetTokenSupplyLiquidityAmount) internal {
uint256 wrappedSetTokenBalance = wrappedSetToken.balanceOf(address(this));
if (_wrappedSetTokenSupplyLiquidityAmount > wrappedSetTokenBalance) {
uint256 wrappedSetTokenIssueAmount = _wrappedSetTokenSupplyLiquidityAmount.sub(wrappedSetTokenBalance);
(address[] memory underlyingAssets ,uint256[] memory underlyingUnits,) = issuanceModule.getRequiredComponentIssuanceUnits(
wrappedSetToken,
wrappedSetTokenIssueAmount
);
require(underlyingAssets.length == 1, "MigrationExtension: invalid wrapped SetToken composition");
require(underlyingAssets[0] == address(aaveToken), "MigrationExtension: wrapped SetToken underlying mismatch");
// Supply underlying for Aave wrapped token
underlyingToken.approve(address(POOL), underlyingUnits[0]);
POOL.supply(
address(underlyingToken),
underlyingUnits[0],
address(this),
0
);
// Issue wrapped SetToken
aaveToken.approve(address(issuanceModule), wrappedSetTokenIssueAmount);
issuanceModule.issue(wrappedSetToken, wrappedSetTokenIssueAmount, address(this));
}
}
/**
* @dev Redeems any excess wrapped SetToken after liquidity decrease
*/
function _redeemExcessWrappedSetToken() internal {
uint256 wrappedSetTokenBalance = wrappedSetToken.balanceOf(address(this));
if (wrappedSetTokenBalance > 0) {
// Redeem wrapped SetToken
wrappedSetToken.approve(address(issuanceModule), wrappedSetTokenBalance);
issuanceModule.redeem(wrappedSetToken, wrappedSetTokenBalance, address(this));
// Withdraw underlying from Aave
uint256 aaveBalance = aaveToken.balanceOf(address(this));
aaveToken.approve(address(POOL), aaveBalance);
POOL.withdraw(
address(underlyingToken),
aaveBalance,
address(this)
);
}
}
/**
* @dev Internal function to mint a new liquidity position in the Uniswap V3 pool.
* Calls Uniswap's `mint` function with specified parameters.
* @param _amount0Desired The desired amount of token0 to be added as liquidity.
* @param _amount1Desired The desired amount of token1 to be added as liquidity.
* @param _amount0Min The minimum amount of token0 to be added as liquidity.
* @param _amount1Min The minimum amount of token1 to be added as liquidity.
* @param _tickLower The lower end of the desired tick range for the position.
* @param _tickUpper The upper end of the desired tick range for the position.
* @param _fee The fee tier of the Uniswap V3 pool in which to add liquidity.
* @param _isUnderlyingToken0 True if the underlying token is token0, false if it is token1.
*/
function _mintLiquidityPosition(
uint256 _amount0Desired,
uint256 _amount1Desired,
uint256 _amount0Min,
uint256 _amount1Min,
int24 _tickLower,
int24 _tickUpper,
uint24 _fee,
bool _isUnderlyingToken0
) internal {
// Sort tokens and amounts
(
address token0,
address token1,
uint256 underlyingAmount,
uint256 wrappedSetTokenAmount
) = _isUnderlyingToken0
? (address(underlyingToken), address(wrappedSetToken), _amount0Desired, _amount1Desired)
: (address(wrappedSetToken), address(underlyingToken), _amount1Desired, _amount0Desired);
// Approve tokens
if (underlyingAmount > 0) {
underlyingToken.approve(address(nonfungiblePositionManager), underlyingAmount);
}
if (wrappedSetTokenAmount > 0) {
wrappedSetToken.approve(address(nonfungiblePositionManager), wrappedSetTokenAmount);
}
// Mint liquidity position
INonfungiblePositionManager.MintParams memory mintParams = INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: _fee,
tickLower: _tickLower,
tickUpper: _tickUpper,
amount0Desired: _amount0Desired,
amount1Desired: _amount1Desired,
amount0Min: _amount0Min,
amount1Min: _amount1Min,
recipient: address(this),
deadline: block.timestamp
});
(uint256 tokenId,,,) = nonfungiblePositionManager.mint(mintParams);
tokenIds.push(tokenId);
}
/**
* @dev Internal function to increase liquidity in a Uniswap V3 pool position.
* Calls Uniswap's `increaseLiquidity` function with specified parameters.
* @param _amount0Desired The desired amount of token0 to be added as liquidity.
* @param _amount1Desired The desired amount of token1 to be added as liquidity.
* @param _amount0Min The minimum amount of token0 to be added as liquidity.
* @param _amount1Min The minimum amount of token1 to be added as liquidity.
* @param _tokenId The ID of the UniV3 LP Token for which liquidity is being increased.
* @param _isUnderlyingToken0 True if the underlying token is token0, false if it is token1.
* @return liquidity The new liquidity amount as a result of the increase.
*/
function _increaseLiquidityPosition(
uint256 _amount0Desired,
uint256 _amount1Desired,
uint256 _amount0Min,
uint256 _amount1Min,
uint256 _tokenId,
bool _isUnderlyingToken0
)
internal
returns (uint128 liquidity)
{
(uint256 underlyingAmount, uint256 wrappedSetTokenAmount) = _isUnderlyingToken0
? (_amount0Desired, _amount1Desired)
: (_amount1Desired, _amount0Desired);
// Approve tokens
if (underlyingAmount > 0) {
underlyingToken.approve(address(nonfungiblePositionManager), underlyingAmount);
}
if (wrappedSetTokenAmount > 0) {
wrappedSetToken.approve(address(nonfungiblePositionManager), wrappedSetTokenAmount);
}
// Increase liquidity
INonfungiblePositionManager.IncreaseLiquidityParams memory increaseParams = INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: _tokenId,
amount0Desired: _amount0Desired,
amount1Desired: _amount1Desired,
amount0Min: _amount0Min,
amount1Min: _amount1Min,
deadline: block.timestamp
});
(liquidity,,) = nonfungiblePositionManager.increaseLiquidity(increaseParams);
}
/**
* @dev Internal function to decrease liquidity and collect fees for a Uniswap V3 position.
* Calls Uniswap's `decreaseLiquidity` and `collect` functions with specified parameters.
* @param _tokenId The ID of the UniV3 LP Token for which liquidity is being decreased.
* @param _liquidity The amount by which liquidity will be decreased.
* @param _amount0Min The minimum amount of token0 that should be accounted for the burned liquidity.
* @param _amount1Min The minimum amount of token1 that should be accounted for the burned liquidity.
*/
function _decreaseLiquidityPosition(
uint256 _tokenId,
uint128 _liquidity,
uint256 _amount0Min,
uint256 _amount1Min
) internal {
// Decrease liquidity
INonfungiblePositionManager.DecreaseLiquidityParams memory decreaseParams = INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: _tokenId,
liquidity: _liquidity,
amount0Min: _amount0Min,
amount1Min: _amount1Min,
deadline: block.timestamp
});
nonfungiblePositionManager.decreaseLiquidity(decreaseParams);
// Collect liquidity and fees
INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams({
tokenId: _tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
});
nonfungiblePositionManager.collect(params);
}
/**
* @dev Internal function to return any remaining unwrapped collateral asset to the operator.
* @return underlyingOutputAmount The amount of unwrapped collateral asset returned to the operator.
*/
function _returnExcessUnderlying() internal returns (uint256 underlyingOutputAmount) {
underlyingOutputAmount = underlyingToken.balanceOf(address(this));
if (underlyingOutputAmount > 0) {
underlyingToken.transfer(msg.sender, underlyingOutputAmount);
}
}
}
MorphoLeverageStrategyExtension.sol 1323 lines
/*
Copyright 2024 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IMorphoLeverageModule } from "../interfaces/IMorphoLeverageModule.sol";
import { IMorphoOracle } from "../interfaces/IMorphoOracle.sol";
import { IMorpho } from "../interfaces/IMorpho.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { StringArrayUtils } from "../lib/StringArrayUtils.sol";
/**
* @title MorphoLeverageStrategyExtension
* @author Index Coop
*
* Smart contract that enables trustless leverage tokens. This extension is paired with the MorphoLeverageModule where module
* interactions are invoked via the IBaseManager contract. Any leveraged token can be constructed as long as there is
* a Morpho Market available with the desired collateral and borrow asset.
* This extension contract also allows the operator to set an ETH reward to incentivize keepers calling the rebalance
* function at different leverage thresholds.
*
*/
contract MorphoLeverageStrategyExtension is BaseExtension {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using SafeCast for int256;
using StringArrayUtils for string[];
uint256 constant public MORPHO_ORACLE_PRICE_SCALE = 1e36;
/* ============ Enums ============ */
enum ShouldRebalance {
NONE, // Indicates no rebalance action can be taken
REBALANCE, // Indicates rebalance() function can be successfully called
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
RIPCORD // Indicates ripcord() function can be successfully called
}
/* ============ Structs ============ */
struct ActionInfo {
uint256 collateralBalance; // Balance of underlying held in Morpho in base units (e.g. USDC 10e6)
uint256 borrowBalance; // Balance of underlying borrowed from Morpho in base units
uint256 collateralValue; // Valuation of collateral in borrow asset base units
uint256 collateralPrice; // Price of collateral relative to borrow asset as returned by morpho oracle
uint256 setTotalSupply; // Total supply of SetToken
uint256 lltv; // Liquidation loan to value ratio of the morpho market
}
struct LeverageInfo {
ActionInfo action;
uint256 currentLeverageRatio; // Current leverage ratio of Set
uint256 slippageTolerance; // Allowable percent trade slippage in preciseUnits (1% = 10^16)
uint256 twapMaxTradeSize; // Max trade size in collateral units allowed for rebalance action
string exchangeName; // Exchange to use for trade
}
struct ContractSettings {
ISetToken setToken; // Instance of leverage token
IMorphoLeverageModule leverageModule; // Instance of Morpho leverage module
address collateralAsset; // Address of underlying collateral
address borrowAsset; // Address of underlying borrow asset
}
struct MethodologySettings {
uint256 targetLeverageRatio; // Long term target ratio in precise units (10e18)
uint256 minLeverageRatio; // In precise units (10e18). If current leverage is below, rebalance target is this ratio
uint256 maxLeverageRatio; // In precise units (10e18). If current leverage is above, rebalance target is this ratio
uint256 recenteringSpeed; // % at which to rebalance back to target leverage in precise units (10e18)
uint256 rebalanceInterval; // Period of time required since last rebalance timestamp in seconds
}
struct ExecutionSettings {
uint256 unutilizedLeveragePercentage; // Percent of max borrow left unutilized in precise units (1% = 10e16)
uint256 unutilizedLeveragePercentageDelever; // Percent of max borrow left unutilized when delevering
uint256 slippageTolerance; // % in precise units to price min token receive amount from trade quantities
uint256 twapCooldownPeriod; // Cooldown period required since last trade timestamp in seconds
}
struct ExchangeSettings {
uint256 twapMaxTradeSize; // Max trade size in collateral base units
uint256 exchangeLastTradeTimestamp; // Timestamp of last trade made with this exchange
uint256 incentivizedTwapMaxTradeSize; // Max trade size for incentivized rebalances in collateral base units
bytes leverExchangeData; // Arbitrary exchange data passed into rebalance function for levering up
bytes deleverExchangeData; // Arbitrary exchange data passed into rebalance function for delevering
}
struct IncentiveSettings {
uint256 etherReward; // ETH reward for incentivized rebalances
uint256 incentivizedLeverageRatio; // Leverage ratio for incentivized rebalances
uint256 incentivizedSlippageTolerance; // Slippage tolerance percentage for incentivized rebalances
uint256 incentivizedTwapCooldownPeriod; // TWAP cooldown in seconds for incentivized rebalances
}
/* ============ Events ============ */
event Engaged(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
);
event Rebalanced(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
);
event RebalanceIterated(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
);
event RipcordCalled(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _rebalanceNotional,
uint256 _etherIncentive
);
event Disengaged(
uint256 _currentLeverageRatio,
uint256 _newLeverageRatio,
uint256 _chunkRebalanceNotional,
uint256 _totalRebalanceNotional
);
event MethodologySettingsUpdated(
uint256 _targetLeverageRatio,
uint256 _minLeverageRatio,
uint256 _maxLeverageRatio,
uint256 _recenteringSpeed,
uint256 _rebalanceInterval
);
event ExecutionSettingsUpdated(
uint256 _unutilizedLeveragePercentage,
uint256 _unutilizedLeveragePercentageDelever,
uint256 _twapCooldownPeriod,
uint256 _slippageTolerance
);
event IncentiveSettingsUpdated(
uint256 _etherReward,
uint256 _incentivizedLeverageRatio,
uint256 _incentivizedSlippageTolerance,
uint256 _incentivizedTwapCooldownPeriod
);
event ExchangeUpdated(
string _exchangeName,
uint256 twapMaxTradeSize,
uint256 exchangeLastTradeTimestamp,
uint256 incentivizedTwapMaxTradeSize,
bytes leverExchangeData,
bytes deleverExchangeData
);
event ExchangeAdded(
string _exchangeName,
uint256 twapMaxTradeSize,
uint256 exchangeLastTradeTimestamp,
uint256 incentivizedTwapMaxTradeSize,
bytes leverExchangeData,
bytes deleverExchangeData
);
event ExchangeRemoved(
string _exchangeName
);
event CollateralWithdrawn();
/* ============ Modifiers ============ */
/**
* Throws if rebalance is currently in TWAP` can be overriden by the operator
*/
modifier noRebalanceInProgress() {
if(!overrideNoRebalanceInProgress) {
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
}
_;
}
/* ============ State Variables ============ */
bool public overrideNoRebalanceInProgress; // Manager controlled flag that allows bypassing the noRebalanceInProgress modifier
ContractSettings internal strategy; // Struct of contracts used in the strategy (SetToken, price oracles, leverage module etc)
MethodologySettings internal methodology; // Struct containing methodology parameters
ExecutionSettings internal execution; // Struct containing execution parameters
mapping(string => ExchangeSettings) internal exchangeSettings; // Mapping from exchange name to exchange settings
IncentiveSettings internal incentive; // Struct containing incentive parameters for ripcord
string[] public enabledExchanges; // Array containing enabled exchanges
uint256 public twapLeverageRatio; // Stored leverage ratio to keep track of target between TWAP rebalances
uint256 public globalLastTradeTimestamp; // Last rebalance timestamp. Current timestamp must be greater than this variable + rebalance interval to rebalance
/* ============ Constructor ============ */
/**
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
*
* @param _manager Address of IBaseManager contract
* @param _strategy Struct of contract addresses
* @param _methodology Struct containing methodology parameters
* @param _execution Struct containing execution parameters
* @param _incentive Struct containing incentive parameters for ripcord
* @param _exchangeNames List of initial exchange names
* @param _exchangeSettings List of structs containing exchange parameters for the initial exchanges
*/
constructor(
IBaseManager _manager,
ContractSettings memory _strategy,
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive,
string[] memory _exchangeNames,
ExchangeSettings[] memory _exchangeSettings
)
public
BaseExtension(_manager)
{
strategy = _strategy;
methodology = _methodology;
execution = _execution;
incentive = _incentive;
for (uint256 i = 0; i < _exchangeNames.length; i++) {
_validateExchangeSettings(_exchangeSettings[i]);
exchangeSettings[_exchangeNames[i]] = _exchangeSettings[i];
enabledExchanges.push(_exchangeNames[i]);
}
_validateNonExchangeSettings(methodology, execution, incentive);
}
/* ============ External Functions ============ */
/**
* OPERATOR ONLY: Enable/Disable override of noRebalanceInProgress modifier
*
* @param _overrideNoRebalanceInProgress Boolean indicating wether to enable / disable override
*/
function setOverrideNoRebalanceInProgress(bool _overrideNoRebalanceInProgress) external onlyOperator {
overrideNoRebalanceInProgress = _overrideNoRebalanceInProgress;
}
/**
* OPERATOR ONLY: Engage to target leverage ratio for the first time. SetToken will borrow debt position from Morpho and trade for collateral asset. If target
* leverage ratio is above max borrow or max trade size, then TWAP is kicked off. To complete engage if TWAP, any valid caller must call iterateRebalance until target
* is met.
*
* @param _exchangeName the exchange used for trading
*/
function engage(string memory _exchangeName) external onlyOperator {
_enterCollateralPosition();
ActionInfo memory engageInfo = _createActionInfo();
require(engageInfo.setTotalSupply > 0, "SetToken must have > 0 supply");
require(engageInfo.collateralBalance > 0, "Collateral balance must be > 0");
require(engageInfo.borrowBalance == 0, "Debt must be 0");
LeverageInfo memory leverageInfo = LeverageInfo({
action: engageInfo,
currentLeverageRatio: PreciseUnitMath.preciseUnit(), // 1x leverage in precise units
slippageTolerance: execution.slippageTolerance,
twapMaxTradeSize: exchangeSettings[_exchangeName].twapMaxTradeSize,
exchangeName: _exchangeName
});
// Calculate total rebalance units and kick off TWAP if above max borrow or max trade size
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _calculateChunkRebalanceNotional(leverageInfo, methodology.targetLeverageRatio, true);
_lever(leverageInfo, chunkRebalanceNotional);
_updateRebalanceState(
chunkRebalanceNotional,
totalRebalanceNotional,
methodology.targetLeverageRatio,
_exchangeName
);
emit Engaged(
leverageInfo.currentLeverageRatio,
methodology.targetLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA AND ALLOWED CALLER: Rebalance product. If current leverage ratio is between the max and min bounds, then rebalance
* can only be called once the rebalance interval has elapsed since last timestamp. If outside the max and min, rebalance can be called anytime to bring leverage
* ratio back to the max or min bounds. The methodology will determine whether to delever or lever.
*
* Note: If the calculated current leverage ratio is above the incentivized leverage ratio or in TWAP then rebalance cannot be called. Instead, you must call
* ripcord() which is incentivized with a reward in Ether or iterateRebalance().
*
* @param _exchangeName the exchange used for trading
*/
function rebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
// use globalLastTradeTimestamps to prevent multiple rebalances being called with different exchanges during the epoch rebalance
_validateNormalRebalance(leverageInfo, methodology.rebalanceInterval, globalLastTradeTimestamp);
_validateNonTWAP();
uint256 newLeverageRatio = _calculateNewLeverageRatio(leverageInfo.currentLeverageRatio);
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _handleRebalance(leverageInfo, newLeverageRatio);
_updateRebalanceState(chunkRebalanceNotional, totalRebalanceNotional, newLeverageRatio, _exchangeName);
emit Rebalanced(
leverageInfo.currentLeverageRatio,
newLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA AND ALLOWED CALLER: Iterate a rebalance when in TWAP. TWAP cooldown period must have elapsed. If price moves advantageously, then exit without rebalancing
* and clear TWAP state. This function can only be called when below incentivized leverage ratio and in TWAP state.
*
* @param _exchangeName the exchange used for trading
*/
function iterateRebalance(string memory _exchangeName) external onlyEOA onlyAllowedCaller(msg.sender) {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
// Use the exchangeLastTradeTimestamp since cooldown periods are measured on a per-exchange basis, allowing it to rebalance multiple time in quick
// succession with different exchanges
_validateNormalRebalance(leverageInfo, execution.twapCooldownPeriod, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
_validateTWAP();
uint256 chunkRebalanceNotional;
uint256 totalRebalanceNotional;
if (!_isAdvantageousTWAP(leverageInfo.currentLeverageRatio)) {
(chunkRebalanceNotional, totalRebalanceNotional) = _handleRebalance(leverageInfo, twapLeverageRatio);
}
// If not advantageous, then rebalance is skipped and chunk and total rebalance notional are both 0, which means TWAP state is
// cleared
_updateIterateState(chunkRebalanceNotional, totalRebalanceNotional, _exchangeName);
emit RebalanceIterated(
leverageInfo.currentLeverageRatio,
twapLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* ONLY EOA: In case the current leverage ratio exceeds the incentivized leverage threshold, the ripcord function can be called by anyone to return leverage ratio
* back to the max leverage ratio. This function typically would only be called during times of high downside volatility and / or normal keeper malfunctions. The caller
* of ripcord() will receive a reward in Ether. The ripcord function uses it's own TWAP cooldown period, slippage tolerance and TWAP max trade size which are typically
* looser than in regular rebalances.
*
* @param _exchangeName the exchange used for trading
*/
function ripcord(string memory _exchangeName) external onlyEOA {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
incentive.incentivizedSlippageTolerance,
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize,
_exchangeName
);
// Use the exchangeLastTradeTimestamp so it can ripcord quickly with multiple exchanges
_validateRipcord(leverageInfo, exchangeSettings[_exchangeName].exchangeLastTradeTimestamp);
( uint256 chunkRebalanceNotional, ) = _calculateChunkRebalanceNotional(leverageInfo, methodology.maxLeverageRatio, false);
_delever(leverageInfo, chunkRebalanceNotional);
_updateRipcordState(_exchangeName);
uint256 etherTransferred = _transferEtherRewardToCaller(incentive.etherReward);
emit RipcordCalled(
leverageInfo.currentLeverageRatio,
methodology.maxLeverageRatio,
chunkRebalanceNotional,
etherTransferred
);
}
/**
* OPERATOR ONLY: Return leverage ratio to 1x and delever to repay loan. This can be used for upgrading or shutting down the strategy. SetToken will redeem
* collateral position and trade for debt position to repay Morpho. If the chunk rebalance size is less than the total notional size, then this function will
* delever and repay entire borrow balance on Morpho. If chunk rebalance size is above max borrow or max trade size, then operator must
* continue to call this function to complete repayment of loan. The function iterateRebalance will not work.
*
* Note: Delever to 0 will likely result in additional units of the borrow asset added as equity on the SetToken due to oracle price / market price mismatch
*
* @param _exchangeName the exchange used for trading
*/
function disengage(string memory _exchangeName) external onlyOperator {
LeverageInfo memory leverageInfo = _getAndValidateLeveragedInfo(
execution.slippageTolerance,
exchangeSettings[_exchangeName].twapMaxTradeSize,
_exchangeName
);
uint256 newLeverageRatio = PreciseUnitMath.preciseUnit();
(
uint256 chunkRebalanceNotional,
uint256 totalRebalanceNotional
) = _calculateChunkRebalanceNotional(leverageInfo, newLeverageRatio, false);
if (totalRebalanceNotional > chunkRebalanceNotional) {
_delever(leverageInfo, chunkRebalanceNotional);
} else {
_deleverToZeroBorrowBalance(leverageInfo, totalRebalanceNotional);
}
emit Disengaged(
leverageInfo.currentLeverageRatio,
newLeverageRatio,
chunkRebalanceNotional,
totalRebalanceNotional
);
}
/**
* OPERATOR ONLY: Withdraw all collateral from morpho
*
* Note: Can only be executed after fully deleveraging / disengaging the strategy. Will revert otherwise.
*/
function exitCollateralPosition() external onlyOperator {
_exitCollateralPosition();
emit CollateralWithdrawn();
}
/**
* OPERATOR ONLY: Set methodology settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newMethodologySettings Struct containing methodology parameters
*/
function setMethodologySettings(MethodologySettings memory _newMethodologySettings) external onlyOperator noRebalanceInProgress {
methodology = _newMethodologySettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit MethodologySettingsUpdated(
methodology.targetLeverageRatio,
methodology.minLeverageRatio,
methodology.maxLeverageRatio,
methodology.recenteringSpeed,
methodology.rebalanceInterval
);
}
/**
* OPERATOR ONLY: Set execution settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newExecutionSettings Struct containing execution parameters
*/
function setExecutionSettings(ExecutionSettings memory _newExecutionSettings) external onlyOperator noRebalanceInProgress {
execution = _newExecutionSettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit ExecutionSettingsUpdated(
execution.unutilizedLeveragePercentage,
execution.unutilizedLeveragePercentageDelever,
execution.twapCooldownPeriod,
execution.slippageTolerance
);
}
/**
* OPERATOR ONLY: Set incentive settings and check new settings are valid. Note: Need to pass in existing parameters if only changing a few settings. Must not be
* in a rebalance.
*
* @param _newIncentiveSettings Struct containing incentive parameters
*/
function setIncentiveSettings(IncentiveSettings memory _newIncentiveSettings) external onlyOperator noRebalanceInProgress {
incentive = _newIncentiveSettings;
_validateNonExchangeSettings(methodology, execution, incentive);
emit IncentiveSettingsUpdated(
incentive.etherReward,
incentive.incentivizedLeverageRatio,
incentive.incentivizedSlippageTolerance,
incentive.incentivizedTwapCooldownPeriod
);
}
/**
* OPERATOR ONLY: Add a new enabled exchange for trading during rebalances. New exchanges will have their exchangeLastTradeTimestamp set to 0. Adding
* exchanges during rebalances is allowed, as it is not possible to enter an unexpected state while doing so.
*
* @param _exchangeName Name of the exchange
* @param _exchangeSettings Struct containing exchange parameters
*/
function addEnabledExchange(
string memory _exchangeName,
ExchangeSettings memory _exchangeSettings
)
external
onlyOperator
{
require(exchangeSettings[_exchangeName].twapMaxTradeSize == 0, "Exchange already enabled");
_validateExchangeSettings(_exchangeSettings);
exchangeSettings[_exchangeName].twapMaxTradeSize = _exchangeSettings.twapMaxTradeSize;
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize = _exchangeSettings.incentivizedTwapMaxTradeSize;
exchangeSettings[_exchangeName].leverExchangeData = _exchangeSettings.leverExchangeData;
exchangeSettings[_exchangeName].deleverExchangeData = _exchangeSettings.deleverExchangeData;
exchangeSettings[_exchangeName].exchangeLastTradeTimestamp = 0;
enabledExchanges.push(_exchangeName);
emit ExchangeAdded(
_exchangeName,
_exchangeSettings.twapMaxTradeSize,
_exchangeSettings.exchangeLastTradeTimestamp,
_exchangeSettings.incentivizedTwapMaxTradeSize,
_exchangeSettings.leverExchangeData,
_exchangeSettings.deleverExchangeData
);
}
/**
* OPERATOR ONLY: Removes an exchange. Reverts if the exchange is not already enabled. Removing exchanges during rebalances is allowed,
* as it is not possible to enter an unexpected state while doing so.
*
* @param _exchangeName Name of exchange to remove
*/
function removeEnabledExchange(string memory _exchangeName) external onlyOperator {
require(exchangeSettings[_exchangeName].twapMaxTradeSize != 0, "Exchange not enabled");
delete exchangeSettings[_exchangeName];
enabledExchanges.removeStorage(_exchangeName);
emit ExchangeRemoved(_exchangeName);
}
/**
* OPERATOR ONLY: Updates the settings of an exchange. Reverts if exchange is not already added. When updating an exchange, exchangeLastTradeTimestamp
* is preserved. Updating exchanges during rebalances is allowed, as it is not possible to enter an unexpected state while doing so. Note: Need to
* pass in all existing parameters even if only changing a few settings.
*
* @param _exchangeName Name of the exchange
* @param _exchangeSettings Struct containing exchange parameters
*/
function updateEnabledExchange(
string memory _exchangeName,
ExchangeSettings memory _exchangeSettings
)
external
onlyOperator
{
require(exchangeSettings[_exchangeName].twapMaxTradeSize != 0, "Exchange not enabled");
_validateExchangeSettings(_exchangeSettings);
exchangeSettings[_exchangeName].twapMaxTradeSize = _exchangeSettings.twapMaxTradeSize;
exchangeSettings[_exchangeName].incentivizedTwapMaxTradeSize = _exchangeSettings.incentivizedTwapMaxTradeSize;
exchangeSettings[_exchangeName].leverExchangeData = _exchangeSettings.leverExchangeData;
exchangeSettings[_exchangeName].deleverExchangeData = _exchangeSettings.deleverExchangeData;
emit ExchangeUpdated(
_exchangeName,
_exchangeSettings.twapMaxTradeSize,
_exchangeSettings.exchangeLastTradeTimestamp,
_exchangeSettings.incentivizedTwapMaxTradeSize,
_exchangeSettings.leverExchangeData,
_exchangeSettings.deleverExchangeData
);
}
/**
* OPERATOR ONLY: Withdraw entire balance of ETH in this contract to operator. Rebalance must not be in progress
*/
function withdrawEtherBalance() external onlyOperator noRebalanceInProgress {
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Ether transfer failed");
}
receive() external payable {}
/* ============ External Getter Functions ============ */
/**
* Get current leverage ratio. Current leverage ratio is defined as the Collateral Value (relative to borrow asset as per morpho oracle) divided by the borrow balance.
*
* return currentLeverageRatio Current leverage ratio in precise units (10e18)
*/
function getCurrentLeverageRatio() public view returns(uint256) {
ActionInfo memory currentLeverageInfo = _createActionInfo();
return _calculateCurrentLeverageRatio(currentLeverageInfo.collateralValue, currentLeverageInfo.borrowBalance);
}
/**
* Calculates the chunk rebalance size. This can be used by external contracts and keeper bots to calculate the optimal exchange to rebalance with.
* Note: this function does not take into account timestamps, so it may return a nonzero value even when shouldRebalance would return ShouldRebalance.NONE for
* all exchanges (since minimum delays have not elapsed)
*
* @param _exchangeNames Array of exchange names to get rebalance sizes for
*
* @return sizes Array of total notional chunk size. Measured in the asset that would be sold
* @return sellAsset Asset that would be sold during a rebalance
* @return buyAsset Asset that would be purchased during a rebalance
*/
function getChunkRebalanceNotional(
string[] calldata _exchangeNames
)
external
view
returns(uint256[] memory sizes, address sellAsset, address buyAsset)
{
uint256 newLeverageRatio;
uint256 currentLeverageRatio = getCurrentLeverageRatio();
bool isRipcord = false;
// if over incentivized leverage ratio, always ripcord
if (currentLeverageRatio > incentive.incentivizedLeverageRatio) {
newLeverageRatio = methodology.maxLeverageRatio;
isRipcord = true;
// if we are in an ongoing twap, use the cached twapLeverageRatio as our target leverage
} else if (twapLeverageRatio > 0) {
newLeverageRatio = twapLeverageRatio;
// if all else is false, then we would just use the normal rebalance new leverage ratio calculation
} else {
newLeverageRatio = _calculateNewLeverageRatio(currentLeverageRatio);
}
ActionInfo memory actionInfo = _createActionInfo();
bool isLever = newLeverageRatio > currentLeverageRatio;
sizes = new uint256[](_exchangeNames.length);
for (uint256 i = 0; i < _exchangeNames.length; i++) {
LeverageInfo memory leverageInfo = LeverageInfo({
action: actionInfo,
currentLeverageRatio: currentLeverageRatio,
slippageTolerance: isRipcord ? incentive.incentivizedSlippageTolerance : execution.slippageTolerance,
twapMaxTradeSize: isRipcord ?
exchangeSettings[_exchangeNames[i]].incentivizedTwapMaxTradeSize :
exchangeSettings[_exchangeNames[i]].twapMaxTradeSize,
exchangeName: _exchangeNames[i]
});
(uint256 collateralNotional, ) = _calculateChunkRebalanceNotional(leverageInfo, newLeverageRatio, isLever);
// _calculateBorrowUnits can convert both unit and notional values
sizes[i] = isLever ? _calculateBorrowUnits(collateralNotional, leverageInfo.action) : collateralNotional;
}
sellAsset = isLever ? strategy.borrowAsset : strategy.collateralAsset;
buyAsset = isLever ? strategy.collateralAsset : strategy.borrowAsset;
}
/**
* Get current Ether incentive for when current leverage ratio exceeds incentivized leverage ratio and ripcord can be called. If ETH balance on the contract is
* below the etherReward, then return the balance of ETH instead.
*
* return etherReward Quantity of ETH reward in base units (10e18)
*/
function getCurrentEtherIncentive() external view returns(uint256) {
uint256 currentLeverageRatio = getCurrentLeverageRatio();
if (currentLeverageRatio >= incentive.incentivizedLeverageRatio) {
// If ETH reward is below the balance on this contract, then return ETH balance on contract instead
return incentive.etherReward < address(this).balance ? incentive.etherReward : address(this).balance;
} else {
return 0;
}
}
/**
* Helper that checks if conditions are met for rebalance or ripcord. Returns an enum with 0 = no rebalance, 1 = call rebalance(), 2 = call iterateRebalance()
* 3 = call ripcord()
*
* @return (string[] memory, ShouldRebalance[] memory) List of exchange names and a list of enums representing whether that exchange should rebalance
*/
function shouldRebalance() external view returns(string[] memory, ShouldRebalance[] memory) {
uint256 currentLeverageRatio = getCurrentLeverageRatio();
return _shouldRebalance(currentLeverageRatio, methodology.minLeverageRatio, methodology.maxLeverageRatio);
}
/**
* Helper that checks if conditions are met for rebalance or ripcord with custom max and min bounds specified by caller. This function simplifies the
* logic for off-chain keeper bots to determine what threshold to call rebalance when leverage exceeds max or drops below min. Returns an enum with
* 0 = no rebalance, 1 = call rebalance(), 2 = call iterateRebalance(), 3 = call ripcord()
*
* @param _customMinLeverageRatio Min leverage ratio passed in by caller
* @param _customMaxLeverageRatio Max leverage ratio passed in by caller
*
* @return (string[] memory, ShouldRebalance[] memory) List of exchange names and a list of enums representing whether that exchange should rebalance
*/
function shouldRebalanceWithBounds(
uint256 _customMinLeverageRatio,
uint256 _customMaxLeverageRatio
)
external
view
returns(string[] memory, ShouldRebalance[] memory)
{
require (
_customMinLeverageRatio <= methodology.minLeverageRatio && _customMaxLeverageRatio >= methodology.maxLeverageRatio,
"Custom bounds must be valid"
);
uint256 currentLeverageRatio = getCurrentLeverageRatio();
return _shouldRebalance(currentLeverageRatio, _customMinLeverageRatio, _customMaxLeverageRatio);
}
/**
* Gets the list of enabled exchanges
*/
function getEnabledExchanges() external view returns (string[] memory) {
return enabledExchanges;
}
/**
* Explicit getter functions for parameter structs are defined as workaround to issues fetching structs that have dynamic types.
*/
function getStrategy() external view returns (ContractSettings memory) { return strategy; }
function getMethodology() external view returns (MethodologySettings memory) { return methodology; }
function getExecution() external view returns (ExecutionSettings memory) { return execution; }
function getIncentive() external view returns (IncentiveSettings memory) { return incentive; }
function getExchangeSettings(string memory _exchangeName) external view returns (ExchangeSettings memory) {
return exchangeSettings[_exchangeName];
}
/* ============ Internal Functions ============ */
function _enterCollateralPosition()
internal
{
bytes memory enterPositionCallData = abi.encodeWithSignature(
"enterCollateralPosition(address)",
address(strategy.setToken)
);
invokeManager(address(strategy.leverageModule), enterPositionCallData);
}
function _exitCollateralPosition()
internal
{
bytes memory exitPositionCallData = abi.encodeWithSignature(
"exitCollateralPosition(address)",
address(strategy.setToken)
);
invokeManager(address(strategy.leverageModule), exitPositionCallData);
}
/**
* Calculate notional rebalance quantity, whether to chunk rebalance based on max trade size and max borrow and invoke lever on MorphoLeverageModule
*
*/
function _lever(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
uint256 collateralRebalanceUnits = _chunkRebalanceNotional.preciseDiv(_leverageInfo.action.setTotalSupply);
uint256 borrowUnits = _calculateBorrowUnits(collateralRebalanceUnits, _leverageInfo.action);
uint256 minReceiveCollateralUnits = _calculateMinCollateralReceiveUnits(collateralRebalanceUnits, _leverageInfo.slippageTolerance);
bytes memory leverCallData = abi.encodeWithSignature(
"lever(address,uint256,uint256,string,bytes)",
address(strategy.setToken),
borrowUnits,
minReceiveCollateralUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].leverExchangeData
);
invokeManager(address(strategy.leverageModule), leverCallData);
}
/**
* Calculate delever units Invoke delever on MorphoLeverageModule.
*/
function _delever(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
uint256 collateralRebalanceUnits = _chunkRebalanceNotional.preciseDiv(_leverageInfo.action.setTotalSupply);
uint256 minRepayUnits = _calculateMinRepayUnits(collateralRebalanceUnits, _leverageInfo.slippageTolerance, _leverageInfo.action);
bytes memory deleverCallData = abi.encodeWithSignature(
"delever(address,uint256,uint256,string,bytes)",
address(strategy.setToken),
collateralRebalanceUnits,
minRepayUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].deleverExchangeData
);
invokeManager(address(strategy.leverageModule), deleverCallData);
}
/**
* Invoke deleverToZeroBorrowBalance on MorphoLeverageModule.
*/
function _deleverToZeroBorrowBalance(
LeverageInfo memory _leverageInfo,
uint256 _chunkRebalanceNotional
)
internal
{
// Account for slippage tolerance in redeem quantity for the deleverToZeroBorrowBalance function
uint256 maxCollateralRebalanceUnits = _chunkRebalanceNotional
.preciseMul(PreciseUnitMath.preciseUnit().add(execution.slippageTolerance))
.preciseDiv(_leverageInfo.action.setTotalSupply);
bytes memory deleverToZeroBorrowBalanceCallData = abi.encodeWithSignature(
"deleverToZeroBorrowBalance(address,uint256,string,bytes)",
address(strategy.setToken),
maxCollateralRebalanceUnits,
_leverageInfo.exchangeName,
exchangeSettings[_leverageInfo.exchangeName].deleverExchangeData
);
invokeManager(address(strategy.leverageModule), deleverToZeroBorrowBalanceCallData);
}
/**
* Check whether to delever or lever based on the current vs new leverage ratios. Used in the rebalance() and iterateRebalance() functions
*
* return uint256 Calculated notional to trade
* return uint256 Total notional to rebalance over TWAP
*/
function _handleRebalance(LeverageInfo memory _leverageInfo, uint256 _newLeverageRatio) internal returns(uint256, uint256) {
uint256 chunkRebalanceNotional;
uint256 totalRebalanceNotional;
if (_newLeverageRatio < _leverageInfo.currentLeverageRatio) {
(
chunkRebalanceNotional,
totalRebalanceNotional
) = _calculateChunkRebalanceNotional(_leverageInfo, _newLeverageRatio, false);
_delever(_leverageInfo, chunkRebalanceNotional);
} else {
(
chunkRebalanceNotional,
totalRebalanceNotional
) = _calculateChunkRebalanceNotional(_leverageInfo, _newLeverageRatio, true);
_lever(_leverageInfo, chunkRebalanceNotional);
}
return (chunkRebalanceNotional, totalRebalanceNotional);
}
/**
* Create the leverage info struct to be used in internal functions
*
* return LeverageInfo Struct containing ActionInfo and other data
*/
function _getAndValidateLeveragedInfo(uint256 _slippageTolerance, uint256 _maxTradeSize, string memory _exchangeName) internal view returns(LeverageInfo memory) {
// Assume if maxTradeSize is 0, then the exchange is not enabled. This is enforced by addEnabledExchange and updateEnabledExchange
require(_maxTradeSize > 0, "Must be valid exchange");
ActionInfo memory actionInfo = _createActionInfo();
require(actionInfo.setTotalSupply > 0, "SetToken must have > 0 supply");
require(actionInfo.collateralBalance > 0, "Collateral balance must be > 0");
require(actionInfo.borrowBalance > 0, "Borrow balance must exist");
// Get current leverage ratio
uint256 currentLeverageRatio = _calculateCurrentLeverageRatio(
actionInfo.collateralValue,
actionInfo.borrowBalance
);
return LeverageInfo({
action: actionInfo,
currentLeverageRatio: currentLeverageRatio,
slippageTolerance: _slippageTolerance,
twapMaxTradeSize: _maxTradeSize,
exchangeName: _exchangeName
});
}
/**
* Create the action info struct to be used in internal functions
*
* return ActionInfo Struct containing data used by internal lever and delever functions
*/
function _createActionInfo() internal view virtual returns(ActionInfo memory) {
ActionInfo memory rebalanceInfo;
IMorpho.MarketParams memory marketParams = strategy.leverageModule.marketParams(strategy.setToken);
// Collateral Price is returned relative to borrow asset with MORPHO_ORACLE_PRICE_SCALE decimal places
rebalanceInfo.collateralPrice = IMorphoOracle(marketParams.oracle).price();
(uint256 collateralBalance, uint256 borrowBalance,) = strategy.leverageModule.getCollateralAndBorrowBalances(strategy.setToken);
rebalanceInfo.collateralBalance = collateralBalance;
rebalanceInfo.borrowBalance = borrowBalance;
rebalanceInfo.collateralValue = rebalanceInfo.collateralPrice.mul(rebalanceInfo.collateralBalance).div(MORPHO_ORACLE_PRICE_SCALE);
rebalanceInfo.setTotalSupply = strategy.setToken.totalSupply();
rebalanceInfo.lltv = marketParams.lltv;
return rebalanceInfo;
}
/**
* Validate non-exchange settings in constructor and setters when updating.
*/
function _validateNonExchangeSettings(
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive
)
internal
virtual
pure
{
require (
_methodology.minLeverageRatio <= _methodology.targetLeverageRatio && _methodology.minLeverageRatio > 0,
"Must be valid min leverage"
);
require (
_methodology.maxLeverageRatio >= _methodology.targetLeverageRatio,
"Must be valid max leverage"
);
require (
_methodology.recenteringSpeed <= PreciseUnitMath.preciseUnit() && _methodology.recenteringSpeed > 0,
"Must be valid recentering speed"
);
require(
_methodology.targetLeverageRatio >= 1 ether,
"Target leverage ratio must be >= 1e18"
);
require (
_execution.unutilizedLeveragePercentage <= PreciseUnitMath.preciseUnit(),
"Unutilized leverage must be <100%"
);
require (
_execution.unutilizedLeveragePercentageDelever <= PreciseUnitMath.preciseUnit(),
"Unutilized leverage on delever must be <100%"
);
require (
_execution.slippageTolerance <= PreciseUnitMath.preciseUnit(),
"Slippage tolerance must be <100%"
);
require (
_incentive.incentivizedSlippageTolerance <= PreciseUnitMath.preciseUnit(),
"Incentivized slippage tolerance must be <100%"
);
require (
_incentive.incentivizedLeverageRatio >= _methodology.maxLeverageRatio,
"Incentivized leverage ratio must be > max leverage ratio"
);
require (
_methodology.rebalanceInterval >= _execution.twapCooldownPeriod,
"Rebalance interval must be greater than TWAP cooldown period"
);
require (
_execution.twapCooldownPeriod >= _incentive.incentivizedTwapCooldownPeriod,
"TWAP cooldown must be greater than incentivized TWAP cooldown"
);
}
/**
* Validate an ExchangeSettings struct when adding or updating an exchange. Does not validate that twapMaxTradeSize < incentivizedMaxTradeSize since
* it may be useful to disable exchanges for ripcord by setting incentivizedMaxTradeSize to 0.
*/
function _validateExchangeSettings(ExchangeSettings memory _settings) internal pure {
require(_settings.twapMaxTradeSize != 0, "Max TWAP trade size must not be 0");
}
/**
* Validate that current leverage is below incentivized leverage ratio and cooldown / rebalance period has elapsed or outsize max/min bounds. Used
* in rebalance() and iterateRebalance() functions
*/
function _validateNormalRebalance(LeverageInfo memory _leverageInfo, uint256 _coolDown, uint256 _lastTradeTimestamp) internal view {
require(_leverageInfo.currentLeverageRatio < incentive.incentivizedLeverageRatio, "Must be below incentivized leverage ratio");
require(
block.timestamp.sub(_lastTradeTimestamp) > _coolDown
|| _leverageInfo.currentLeverageRatio > methodology.maxLeverageRatio
|| _leverageInfo.currentLeverageRatio < methodology.minLeverageRatio,
"Cooldown not elapsed or not valid leverage ratio"
);
}
/**
* Validate that current leverage is above incentivized leverage ratio and incentivized cooldown period has elapsed in ripcord()
*/
function _validateRipcord(LeverageInfo memory _leverageInfo, uint256 _lastTradeTimestamp) internal view {
require(_leverageInfo.currentLeverageRatio >= incentive.incentivizedLeverageRatio, "Must be above incentivized leverage ratio");
// If currently in the midst of a TWAP rebalance, ensure that the cooldown period has elapsed
require(_lastTradeTimestamp.add(incentive.incentivizedTwapCooldownPeriod) < block.timestamp, "TWAP cooldown must have elapsed");
}
/**
* Validate TWAP in the iterateRebalance() function
*/
function _validateTWAP() internal view {
require(twapLeverageRatio > 0, "Not in TWAP state");
}
/**
* Validate not TWAP in the rebalance() function
*/
function _validateNonTWAP() internal view {
require(twapLeverageRatio == 0, "Must call iterate");
}
/**
* Check if price has moved advantageously while in the midst of the TWAP rebalance. This means the current leverage ratio has moved over/under
* the stored TWAP leverage ratio on lever/delever so there is no need to execute a rebalance. Used in iterateRebalance()
*/
function _isAdvantageousTWAP(uint256 _currentLeverageRatio) internal view returns (bool) {
return (
(twapLeverageRatio < methodology.targetLeverageRatio && _currentLeverageRatio >= twapLeverageRatio)
|| (twapLeverageRatio > methodology.targetLeverageRatio && _currentLeverageRatio <= twapLeverageRatio)
);
}
/**
* Calculate the current leverage ratio given a valuation of the collateral in terms of the borrow asset
* and the borrow balance
*
* return uint256 Current leverage ratio
*/
function _calculateCurrentLeverageRatio(
uint256 _collateralValue,
uint256 _borrowBalance
)
internal
pure
returns(uint256)
{
return _collateralValue.preciseDiv(_collateralValue.sub(_borrowBalance));
}
/**
* Calculate the new leverage ratio. The methodology reduces the size of each rebalance by weighting
* the current leverage ratio against the target leverage ratio by the recentering speed percentage. The lower the recentering speed, the slower
* the leverage token will move towards the target leverage each rebalance.
*
* return uint256 New leverage ratio
*/
function _calculateNewLeverageRatio(uint256 _currentLeverageRatio) internal view returns(uint256) {
// CLRt+1 = max(MINLR, min(MAXLR, CLRt * (1 - RS) + TLR * RS))
// a: TLR * RS
// b: (1- RS) * CLRt
// c: (1- RS) * CLRt + TLR * RS
// d: min(MAXLR, CLRt * (1 - RS) + TLR * RS)
uint256 a = methodology.targetLeverageRatio.preciseMu...
// [truncated — 60586 bytes total]
OptimisticAuctionRebalanceExtensionV1.sol 411 lines
/*
Copyright 2023 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { AncillaryData } from "../lib/AncillaryData.sol";
import { AssetAllowList } from "../lib/AssetAllowList.sol";
import { AuctionRebalanceExtension } from "./AuctionRebalanceExtension.sol";
import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { OptimisticOracleV3Interface } from "../interfaces/OptimisticOracleV3Interface.sol";
/**
* @title OptimisticAuctionRebalanceExtension
* @author Index Coop
*
* @dev The contract extends `AuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances.
* It allows setting product-specific parameters for optimistic rebalancing and includes callback functions for resolved or disputed assertions.
* @dev Version 1 is characterised by: Optional Asset Whitelist, No Set Token locking, control over rebalance timing via "isOpen" flag
*/
contract OptimisticAuctionRebalanceExtensionV1 is AuctionRebalanceExtension, AssetAllowList {
using AddressArrayUtils for address[];
using SafeERC20 for IERC20;
/* ============ Events ============ */
event ProductSettingsUpdated(
IERC20 indexed setToken,
address indexed manager,
OptimisticRebalanceParams optimisticParams,
string rules
);
event RebalanceProposed(
ISetToken indexed setToken,
IERC20 indexed quoteAsset,
address[] oldComponents,
address[] newComponents,
AuctionExecutionParams[] newComponentsAuctionParams,
AuctionExecutionParams[] oldComponentsAuctionParams,
uint256 rebalanceDuration,
uint256 positionMultiplier
);
event AssertedClaim(
IERC20 indexed setToken,
address indexed _assertedBy,
string rules,
bytes32 _assertionId,
bytes _claimData
);
event ProposalDeleted(
bytes32 assertionID,
bytes32 indexed proposalHash
);
event IsOpenUpdated(
bool indexed isOpen
);
/* ============ Structs ============ */
struct AuctionExtensionParams {
IBaseManager baseManager; // Manager Contract of the set token for which to deploy this extension
IAuctionRebalanceModuleV1 auctionModule; // Contract that rebalances index sets via single-asset auctions
bool useAssetAllowlist; // Bool indicating whether to use asset allow list
address[] allowedAssets; // Array of allowed assets
}
struct OptimisticRebalanceParams{
IERC20 collateral; // Collateral currency used to assert proposed transactions.
uint64 liveness; // The amount of time to dispute proposed transactions before they can be executed.
uint256 bondAmount; // Configured amount of collateral currency to make assertions for proposed transactions.
bytes32 identifier; // Identifier used to request price from the DVM.
OptimisticOracleV3Interface optimisticOracleV3; // Optimistic Oracle V3 contract used to assert proposed transactions.
}
struct ProductSettings{
OptimisticRebalanceParams optimisticParams; // OptimisticRebalanceParams struct containing optimistic rebalance parameters.
string rules; // Definition of rules for the product. (including ipfs hash pointing to full rules)
}
/* ============ State Variables ============ */
ProductSettings public productSettings;
mapping(bytes32 => bytes32) public assertionIds; // Maps proposal hashes to assertionIds.
mapping(bytes32 => bytes32) public assertionIdToProposalHash; // Maps assertionIds to a proposal hash.
bool public isOpen; // Bool indicating whether the extension is open for proposing rebalances.
// Keys for assertion claim data.
bytes public constant PROPOSAL_HASH_KEY = "proposalHash";
bytes public constant RULES_KEY = "rules";
/* ============ Constructor ============ */
/*
* @dev Initializes the OptimisticAuctionRebalanceExtension with the passed parameters.
*
* @param _auctionParams AuctionExtensionParams struct containing the baseManager and auctionModule addresses.
*/
constructor(
AuctionExtensionParams memory _auctionParams
)
public
AuctionRebalanceExtension(_auctionParams.baseManager, _auctionParams.auctionModule)
AssetAllowList(_auctionParams.allowedAssets, _auctionParams.useAssetAllowlist)
{}
/* ============ Modifier ============ */
modifier onlyIfOpen() {
require(isOpen, "Must be open for rebalancing");
_;
}
/* ============ External Functions ============ */
/**
* ONLY OPERATOR: Add new asset(s) that can be included as new components in rebalances
*
* @param _assets New asset(s) to add
*/
function addAllowedAssets(address[] memory _assets) external onlyOperator {
_addAllowedAssets(_assets);
}
/**
* ONLY OPERATOR: Remove asset(s) so that it/they can't be included as new components in rebalances
*
* @param _assets Asset(s) to remove
*/
function removeAllowedAssets(address[] memory _assets) external onlyOperator {
_removeAllowedAssets(_assets);
}
/**
* ONLY OPERATOR: Toggle useAssetAllowlist on and off. When false asset allowlist is ignored
* when true it is enforced.
*
* @param _useAssetAllowlist Bool indicating whether to use asset allow list
*/
function updateUseAssetAllowlist(bool _useAssetAllowlist) external onlyOperator {
_updateUseAssetAllowlist(_useAssetAllowlist);
}
/**
* ONLY OPERATOR: Toggle isOpen on and off. When false the extension is closed for proposing rebalances.
* when true it is open.
*
* @param _isOpen Bool indicating whether the extension is open for proposing rebalances.
*/
function updateIsOpen(bool _isOpen) external onlyOperator {
_updateIsOpen(_isOpen);
}
/**
* @dev OPERATOR ONLY: sets product settings for a given set token
* @param _optimisticParams OptimisticRebalanceParams struct containing optimistic rebalance parameters.
* @param _rules string describing the rules of the rebalance (including IPFS hash pointing to full rules)
*/
function setProductSettings(
OptimisticRebalanceParams memory _optimisticParams,
string memory _rules
)
external
onlyOperator
{
productSettings = ProductSettings({
optimisticParams: _optimisticParams,
rules: _rules
});
emit ProductSettingsUpdated(setToken, setToken.manager(), _optimisticParams, _rules);
}
/**
* @dev IF OPEN ONLY: Proposes a rebalance for the SetToken using the Optimistic Oracle V3.
*
* @param _quoteAsset ERC20 token used as the quote asset in auctions.
* @param _oldComponents Addresses of existing components in the SetToken.
* @param _newComponents Addresses of new components to be added.
* @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents.
* @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to
* the current component positions. Set to 0 for components being removed.
* @param _rebalanceDuration Duration of the rebalance in seconds.
* @param _positionMultiplier Position multiplier at the time target units were calculated.
*/
function proposeRebalance(
IERC20 _quoteAsset,
address[] memory _oldComponents,
address[] memory _newComponents,
AuctionExecutionParams[] memory _newComponentsAuctionParams,
AuctionExecutionParams[] memory _oldComponentsAuctionParams,
uint256 _rebalanceDuration,
uint256 _positionMultiplier
)
external
onlyAllowedAssets(_newComponents)
onlyIfOpen()
{
bytes32 proposalHash = keccak256(abi.encode(
setToken,
_quoteAsset,
_oldComponents,
_newComponents,
_newComponentsAuctionParams,
_oldComponentsAuctionParams,
false, // We don't allow locking the set token in this version
_rebalanceDuration,
_positionMultiplier
));
require(assertionIds[proposalHash] == bytes32(0), "Proposal already exists");
require(bytes(productSettings.rules).length > 0, "Rules not set");
require(address(productSettings.optimisticParams.optimisticOracleV3) != address(0), "Oracle not set");
bytes memory claim = _constructClaim(proposalHash, productSettings.rules);
uint256 totalBond = _pullBond(productSettings.optimisticParams);
bytes32 assertionId = productSettings.optimisticParams.optimisticOracleV3.assertTruth(
claim,
msg.sender,
address(this),
address(0),
productSettings.optimisticParams.liveness,
productSettings.optimisticParams.collateral,
totalBond,
productSettings.optimisticParams.identifier,
bytes32(0)
);
assertionIds[proposalHash] = assertionId;
assertionIdToProposalHash[assertionId] = proposalHash;
emit RebalanceProposed( setToken, _quoteAsset, _oldComponents, _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, _rebalanceDuration, _positionMultiplier);
emit AssertedClaim(setToken, msg.sender, productSettings.rules, assertionId, claim);
}
/**
* @dev OPERATOR ONLY: Checks that the old components array matches the current components array and then invokes the
* AuctionRebalanceModuleV1 startRebalance function.
*
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _quoteAsset ERC20 token used as the quote asset in auctions.
* @param _oldComponents Addresses of existing components in the SetToken.
* @param _newComponents Addresses of new components to be added.
* @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents.
* @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to
* the current component positions. Set to 0 for components being removed.
* @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken. Has to be false in this version
* @param _rebalanceDuration Duration of the rebalance in seconds.
* @param _positionMultiplier Position multiplier at the time target units were calculated.
*/
function startRebalance(
IERC20 _quoteAsset,
address[] memory _oldComponents,
address[] memory _newComponents,
AuctionExecutionParams[] memory _newComponentsAuctionParams,
AuctionExecutionParams[] memory _oldComponentsAuctionParams,
bool _shouldLockSetToken,
uint256 _rebalanceDuration,
uint256 _positionMultiplier
)
external
override
onlyIfOpen()
{
bytes32 proposalHash = keccak256(abi.encode(
setToken,
_quoteAsset,
_oldComponents,
_newComponents,
_newComponentsAuctionParams,
_oldComponentsAuctionParams,
_shouldLockSetToken,
_rebalanceDuration,
_positionMultiplier
));
bytes32 assertionId = assertionIds[proposalHash];
// Disputed assertions are expected to revert here. Assumption past this point is that there was a valid assertion.
require(assertionId != bytes32(0), "Proposal hash does not exist");
_deleteProposal(assertionId);
// There is no need to check the assertion result as this point can be reached only for non-disputed assertions.
// It is expected that future versions of the Optimistic Oracle will always revert here,
// if the assertionId has not been settled and can not currently be settled.
productSettings.optimisticParams.optimisticOracleV3.settleAndGetAssertionResult(assertionId);
address[] memory currentComponents = setToken.getComponents();
require(currentComponents.length == _oldComponents.length, "Mismatch: old and current components length");
for (uint256 i = 0; i < _oldComponents.length; i++) {
require(currentComponents[i] == _oldComponents[i], "Mismatch: old and current components");
}
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.startRebalance.selector,
setToken,
_quoteAsset,
_newComponents,
_newComponentsAuctionParams,
_oldComponentsAuctionParams,
_shouldLockSetToken,
_rebalanceDuration,
_positionMultiplier
);
invokeManager(address(auctionModule), callData);
_updateIsOpen(false);
}
/**
* @notice Callback function that is called by Optimistic Oracle V3 when an assertion is resolved.
* @dev This function does nothing and is only here to satisfy the callback recipient interface.
* @param assertionId The identifier of the assertion that was resolved.
* @param assertedTruthfully Whether the assertion was resolved as truthful or not.
*/
function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) external {}
/**
* @notice Callback to automatically delete a proposal that was disputed.
* @param _assertionId the identifier of the disputed assertion.
*/
function assertionDisputedCallback(bytes32 _assertionId) external {
bytes32 proposalHash = assertionIdToProposalHash[_assertionId];
require(address(productSettings.optimisticParams.optimisticOracleV3) != address(0), "Invalid oracle address");
// If the sender is the Optimistic Oracle V3, delete the proposal and associated assertionId.
if (msg.sender == address(productSettings.optimisticParams.optimisticOracleV3)) {
// Delete the disputed proposal and associated assertionId.
_deleteProposal(_assertionId);
} else {
// If the sender is not the expected Optimistic Oracle V3, check if the expected Oracle has the assertion and if not delete.
require(proposalHash != bytes32(0), "Invalid proposal hash");
require(productSettings.optimisticParams.optimisticOracleV3.getAssertion(_assertionId).asserter == address(0), "Oracle has assertion");
_deleteProposal(_assertionId);
}
emit ProposalDeleted(_assertionId, proposalHash);
}
/* ============ Internal Functions ============ */
// Constructs the claim that will be asserted at the Optimistic Oracle V3.
// @dev Inspired by the equivalent function in the OptimisticGovernor: https://github.com/UMAprotocol/protocol/blob/96cf5be32a3f57ac761f004890dd3466c63e1fa5/packages/core/contracts/optimistic-governor/implementation/OptimisticGovernor.sol#L437
function _constructClaim(bytes32 proposalHash, string memory rules) internal pure returns (bytes memory) {
return
abi.encodePacked(
AncillaryData.appendKeyValueBytes32("", PROPOSAL_HASH_KEY, proposalHash),
",",
RULES_KEY,
":\"",
rules,
"\""
);
}
/// @notice Delete an existing proposal and associated assertionId.
/// @dev Internal function that deletes a proposal and associated assertionId.
/// @param assertionId assertionId of the proposal to delete.
function _deleteProposal(bytes32 assertionId) internal {
bytes32 proposalHash = assertionIdToProposalHash[assertionId];
delete assertionIds[proposalHash];
delete assertionIdToProposalHash[assertionId];
}
/// @notice Pulls the higher of the minimum bond or configured bond amount from the sender.
/// @dev Internal function to pull the user's bond before asserting a claim.
/// @param optimisticRebalanceParams optimistic rebalance parameters for the product.
/// @return Bond amount pulled from the sender.
function _pullBond(OptimisticRebalanceParams memory optimisticRebalanceParams) internal returns (uint256) {
uint256 minimumBond = optimisticRebalanceParams.optimisticOracleV3.getMinimumBond(address(optimisticRebalanceParams.collateral));
uint256 totalBond = minimumBond > optimisticRebalanceParams.bondAmount ? minimumBond : optimisticRebalanceParams.bondAmount;
optimisticRebalanceParams.collateral.safeTransferFrom(msg.sender, address(this), totalBond);
optimisticRebalanceParams.collateral.safeIncreaseAllowance(address(optimisticRebalanceParams.optimisticOracleV3), totalBond);
return totalBond;
}
function _updateIsOpen(bool _isOpen) internal {
isOpen = _isOpen;
emit IsOpenUpdated(_isOpen);
}
}
PrtFeeSplitExtension.sol 228 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { FeeSplitExtension } from "./FeeSplitExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IIssuanceModule } from "../interfaces/IIssuanceModule.sol";
import { IPrt } from "../interfaces/IPrt.sol";
import { IPrtStakingPool } from "../interfaces/IPrtStakingPool.sol";
import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title PrtFeeSplitExtension
* @dev Extension that allows for splitting and setting streaming and mint/redeem fees with a
* PRT Staking Pool. The operator can accrue fees from the streaming fee module and distribute
* them to the operator and the PRT Staking Pool, snapshotting the PRT Staking Pool. The operator
* can update the PRT staking pool address and the fee split between the operator and the
* PRT staking pool. Includes an optional allow list and timelock on accrue function.
*/
contract PrtFeeSplitExtension is FeeSplitExtension {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Events ============ */
event AnyoneAccrueUpdated(bool isAnyoneAllowedToAccrue);
event AccruerStatusUpdated(address indexed accruer, bool isAccruerAllowed);
event OperatorFeeSplitUpdated(uint256 newFeeSplit);
event PrtFeesDistributed(
address indexed operatorFeeRecipient,
address indexed prtStakingPool,
uint256 operatorTake,
uint256 prtTake
);
event PrtStakingPoolUpdated(address newPrtStakingPool);
/* ============ Immutables ============ */
IPrt public immutable prt;
/* ============ State Variables ============ */
bool public isAnyoneAllowedToAccrue;
address[] accrueAllowList;
mapping(address => bool) public accrueAllowMap;
IPrtStakingPool public prtStakingPool;
/* ============ Modifiers ============ */
modifier onlyAllowedAccruer() {
require(_isAllowedAccruer(msg.sender), "Not allowed to accrue");
_;
}
/* ============ Constructor ============ */
constructor(
IBaseManager _manager,
IStreamingFeeModule _streamingFeeModule,
IIssuanceModule _issuanceModule,
uint256 _operatorFeeSplit,
address _operatorFeeRecipient,
IPrt _prt
)
public
FeeSplitExtension(
_manager,
_streamingFeeModule,
_issuanceModule,
_operatorFeeSplit,
_operatorFeeRecipient
)
{
require(_prt.setToken() == address(manager.setToken()), "SetToken mismatch with Prt");
prt = _prt;
}
/* ============ External Functions ============ */
/**
* @notice MUTUAL UPGRADE: Updates PRT staking pool. PRT staking pool must have this extension set as the feeSplitExtension.
* @param _prtStakingPool Address of the new PRT staking pool
*/
function updatePrtStakingPool(IPrtStakingPool _prtStakingPool)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
require(address(_prtStakingPool) != address(0), "Zero address not valid");
require(_prtStakingPool.distributor() == address(this), "PRT Staking Pool distributor must be this extension");
require(_prtStakingPool.stakeToken() == address(prt), "PRT Staking Pool stake token must be PRT");
require(_prtStakingPool.rewardToken() == address(manager.setToken()), "PRT Staking Pool reward token must be SetToken");
prtStakingPool = _prtStakingPool;
emit PrtStakingPoolUpdated(address(_prtStakingPool));
}
/**
* @notice ONLY ALLOWED ACCRUER: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for
* operator and PRT staking pool, and sends to operator fee recipient and PRT Staking Pool respectively. NOTE: mint/redeem fees
* will automatically be sent to this address so reading the balance of the SetToken in the contract after accrual is
* sufficient for accounting for all collected fees. If the PRT take is greater than 0, the PRT Staking Pool will accrue the fees
* and update the snapshot.
*/
function accrueFeesAndDistribute() public override onlyAllowedAccruer {
require(address(prtStakingPool) != address(0), "PRT Staking Pool not set");
// Emits a FeeActualized event
streamingFeeModule.accrueFee(setToken);
uint256 totalFees = setToken.balanceOf(address(this));
uint256 operatorTake = totalFees.preciseMul(operatorFeeSplit);
uint256 prtTake = totalFees.sub(operatorTake);
if (operatorTake > 0) {
setToken.transfer(operatorFeeRecipient, operatorTake);
}
// Accrue PRT Staking Pool rewards and update snapshot
if (prtTake > 0) {
setToken.approve(address(prtStakingPool), prtTake);
prtStakingPool.accrue(prtTake);
}
emit PrtFeesDistributed(operatorFeeRecipient, address(prtStakingPool), operatorTake, prtTake);
}
/**
* @notice MUTUAL UPGRADE: Updates fee split between operator and PRT Staking Pool. Split defined in precise units (1% = 10^16).
* Does not accrue fees and snapshot PRT Staking Pool.
* @param _newFeeSplit Percent of fees in precise units (10^16 = 1%) sent to operator, (rest go to the PRT Staking Pool).
*/
function updateFeeSplit(uint256 _newFeeSplit)
external
override
mutualUpgrade(manager.operator(), manager.methodologist())
{
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%");
operatorFeeSplit = _newFeeSplit;
emit OperatorFeeSplitUpdated(_newFeeSplit);
}
/**
* @notice ONLY OPERATOR: Toggles the permission status of specified addresses to call the `accrueFeesAndDistribute()` function.
* @param _accruers An array of addresses whose accrue permission status is to be toggled.
* @param _statuses An array of booleans indicating the new accrue permission status for each corresponding address in `_accruers`.
*/
function setAccruersStatus(
address[] memory _accruers,
bool[] memory _statuses
)
external
onlyOperator
{
_accruers.validatePairsWithArray(_statuses);
for (uint256 i = 0; i < _accruers.length; i++) {
_updateAccrueAllowList(_accruers[i], _statuses[i]);
accrueAllowMap[_accruers[i]] = _statuses[i];
emit AccruerStatusUpdated(_accruers[i], _statuses[i]);
}
}
/**
* @notice ONLY OPERATOR: Toggles whether or not anyone is allowed to call the `accrueFeesAndDistribute()` function.
* If set to true, it bypasses the accrueAllowList, allowing any address to call the `accrueFeesAndDistribute()` function.
* @param _status A boolean indicating if anyone can accrue.
*/
function updateAnyoneAccrue(bool _status)
external
onlyOperator
{
isAnyoneAllowedToAccrue = _status;
emit AnyoneAccrueUpdated(_status);
}
/**
* @notice Determines whether the given address is permitted to `accrueFeesAndDistribute()`.
* @param _accruer Address of the accruer.
* @return bool True if the given `_accruer` is permitted to accrue, false otherwise.
*/
function isAllowedAccruer(address _accruer) external view returns (bool) {
return _isAllowedAccruer(_accruer);
}
/**
* @dev Retrieves the list of addresses that are permitted to `accrueFeesAndDistribute()`.
* @return address[] Array of addresses representing the allowed accruers.
*/
function getAllowedAccruers() external view returns (address[] memory) {
return accrueAllowList;
}
/* ============ Internal Functions ============ */
function _isAllowedAccruer(address _accruer) internal view returns (bool) {
return isAnyoneAllowedToAccrue || accrueAllowMap[_accruer];
}
function _updateAccrueAllowList(address _accruer, bool _status) internal {
if (_status && !accrueAllowList.contains(_accruer)) {
accrueAllowList.push(_accruer);
} else if(!_status && accrueAllowList.contains(_accruer)) {
accrueAllowList.removeStorage(_accruer);
}
}
}
ReinvestmentExtensionV1.sol 235 lines
/*
Copyright 2024 Index Cooperative.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IAirdropModule } from "../interfaces/IAirdropModule.sol";
import { ITradeModule } from "../interfaces/ITradeModule.sol";
import { IWrapModuleV2 } from "../interfaces/IWrapModuleV2.sol";
/**
* @title ReinvestmentExtensionV1
* @author Index Cooperative
*/
contract ReinvestmentExtensionV1 is BaseExtension {
using Address for address;
using SafeCast for int256;
/* ========== Structs ================= */
struct ExchangeSettings {
string exchangeName;
bytes exchangeCallData;
}
/* ========== State Variables ========= */
address public immutable WETH;
ISetToken public immutable setToken;
IAirdropModule public immutable airdropModule;
ITradeModule public immutable tradeModule;
IWrapModuleV2 public immutable wrapModule;
mapping(address => ExchangeSettings) public exchangeSettings;
mapping(address => mapping(address => bool)) public approvedWrapPairs;
/* ============ Constructor ============ */
constructor(
IBaseManager _manager,
address _weth,
IAirdropModule _airdropModule,
ITradeModule _tradeModule,
IWrapModuleV2 _wrapModule,
address[] memory _initialRewardTokens,
ExchangeSettings[] memory _initialExchangeSettings,
address[][] memory _initialWrapPairs
) public BaseExtension(_manager) {
require(_weth != address(0), "Invalid WETH address");
setToken = _manager.setToken();
WETH = _weth;
airdropModule = _airdropModule;
tradeModule = _tradeModule;
wrapModule = _wrapModule;
require(_initialRewardTokens.length == _initialExchangeSettings.length, "Arrays length mismatch");
for (uint256 i = 0; i < _initialRewardTokens.length; i++) {
require(_initialRewardTokens[i] != address(0), "Invalid reward token");
exchangeSettings[_initialRewardTokens[i]] = _initialExchangeSettings[i];
}
for (uint256 i = 0; i < _initialWrapPairs.length; i++) {
address underlyingToken = _initialWrapPairs[i][0];
address wrappedToken = _initialWrapPairs[i][1];
require(underlyingToken != address(0) && wrappedToken != address(0), "Invalid token address");
approvedWrapPairs[underlyingToken][wrappedToken] = true;
}
}
/* ============ External Functions ============ */
/**
* APPROVED_CALLER ONLY: Absorbs airdropped tokens and trades them for WETH
*
* @param _rewardToken Address of reward token to reinvest
* @param _minReceiveQuantity Minimum amount of WETH to receive
*/
function reinvest(
address _rewardToken,
uint256 _minReceiveQuantity
) external onlyAllowedCaller(msg.sender) {
bytes memory absorbCallData = abi.encodeWithSelector(
IAirdropModule.absorb.selector,
setToken,
_rewardToken
);
invokeManager(address(airdropModule), absorbCallData);
uint256 rewardUnits = uint256(setToken.getTotalComponentRealUnits(_rewardToken));
require(rewardUnits > 0, "Reward units must be greater than zero");
bytes memory tradeCallData = abi.encodeWithSelector(
ITradeModule.trade.selector,
setToken,
exchangeSettings[_rewardToken].exchangeName,
_rewardToken,
rewardUnits,
WETH,
_minReceiveQuantity,
exchangeSettings[_rewardToken].exchangeCallData
);
invokeManager(address(tradeModule), tradeCallData);
}
/**
* OPERATOR ONLY: Wraps underlying token into target wrapped token
*
* @param _underlyingToken Address of underlying token
* @param _wrappedToken Address of wrapped token
* @param _underlyingUnits Units of underlying token to wrap
* @param _integrationName Name of wrap module integration
* @param _wrapData Encoded wrap data
*/
function wrap(
address _underlyingToken,
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName,
bytes memory _wrapData
) external onlyAllowedCaller(msg.sender) {
require(_underlyingUnits > 0, "Invalid units");
require(approvedWrapPairs[_underlyingToken][_wrappedToken], "Unapproved wrap pair");
bytes memory wrapCallData = abi.encodeWithSelector(
wrapModule.wrap.selector,
setToken,
_underlyingToken,
_wrappedToken,
_underlyingUnits,
_integrationName,
_wrapData
);
invokeManager(address(wrapModule), wrapCallData);
}
/**
* OPERATOR ONLY: Adds new token to airdrop list
*
* @param _token Address of token to add to airdrop list
*/
function addAirdrop(address _token) external onlyOperator {
invokeManager(
address(airdropModule),
abi.encodeWithSignature("addAirdrop(address,address)", setToken, _token)
);
}
/**
* OPERATOR ONLY: Updates exchange settings for a reward token
*/
function updateExchangeSettings(
address _rewardToken,
ExchangeSettings memory _settings
) external onlyOperator {
require(_rewardToken != address(0), "Invalid reward token");
exchangeSettings[_rewardToken] = _settings;
}
/**
* OPERATOR ONLY: Adds an approved wrap pair
*
* @param _underlyingToken Address of underlying token
* @param _wrappedToken Address of wrapped token
*/
function addWrapPair(address _underlyingToken, address _wrappedToken) external onlyOperator {
require(_underlyingToken != address(0) && _wrappedToken != address(0), "Invalid token address");
require(!approvedWrapPairs[_underlyingToken][_wrappedToken], "Pair already exists");
approvedWrapPairs[_underlyingToken][_wrappedToken] = true;
}
/**
* OPERATOR ONLY: Removes an approved wrap pair
*
* @param _underlyingToken Address of underlying token
* @param _wrappedToken Address of wrapped token
*/
function removeWrapPair(address _underlyingToken, address _wrappedToken) external onlyOperator {
require(_underlyingToken != address(0) && _wrappedToken != address(0), "Invalid token address");
require(approvedWrapPairs[_underlyingToken][_wrappedToken], "Pair does not exist");
delete approvedWrapPairs[_underlyingToken][_wrappedToken];
}
/**
* OPERATOR ONLY: Initializes the AirdropModule
*
* @param _airdropSettings Airdrop module initialization settings
*/
function initializeAirdropModule(IAirdropModule.AirdropSettings memory _airdropSettings) external onlyOperator {
bytes memory callData = abi.encodeWithSelector(
airdropModule.initialize.selector,
setToken,
_airdropSettings
);
invokeManager(address(airdropModule), callData);
}
/**
* OPERATOR ONLY: Initializes the TradeModule
*/
function initializeTradeModule() external onlyOperator {
bytes memory tradeModuleData = abi.encodeWithSelector(tradeModule.initialize.selector, setToken);
invokeManager(address(tradeModule), tradeModuleData);
}
/**
* OPERATOR ONLY: Initializes the WrapModule
*/
function initializeWrapModule() external onlyOperator {
bytes memory wrapModuleData = abi.encodeWithSelector(wrapModule.initialize.selector, setToken);
invokeManager(address(wrapModule), wrapModuleData);
}
}
StakeWiseReinvestmentExtension.sol 146 lines
/*
Copyright 2022 Index Cooperative.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IAirdropModule } from "../interfaces/IAirdropModule.sol";
import { ITradeModule } from "../interfaces/ITradeModule.sol";
/**
* @title StakeWiseReinvestmentExtension
* @author FlattestWhite
*
* Smart contract that enables reinvesting the accrued rETH2 into a SetToken into sETH2.
*/
contract StakeWiseReinvestmentExtension is BaseExtension {
using Address for address;
using SafeCast for int256;
/* ============ Constants ============= */
address public constant S_ETH2 = 0xFe2e637202056d30016725477c5da089Ab0A043A;
address public constant R_ETH2 = 0x20BC832ca081b91433ff6c17f85701B6e92486c5;
/* ========== Structs ================= */
struct ExecutionSettings {
// Name of the exchange adapter stored in the IntegrationRegistry. Typically the name of the contract
// such as UniswapV3ExchangeAdapterV2.
string exchangeName;
// The callData that needs to be passed along to the exchange adapter. This is usually generated with
// a external view call on the adapter contract using the function generateDataParam.
bytes exchangeCallData;
}
/* ========== State Variables ========= */
ISetToken public immutable setToken;
IAirdropModule public immutable airdropModule;
ITradeModule public immutable tradeModule;
ExecutionSettings public settings;
/* ============ Constructor ============ */
/**
* Sets state variables
*
* @param _manager // The manager contract. Used to invoke calls on the underlying SetToken.
* @param _airdropModule // The airdropModule contract. Used to absorb tokens into the SetToken so that it's part of SetToken's accounting.
* @param _tradeModule // The tradeModule contract. Used to trade the absorbed rETH2 into sETH2.
* @param _settings // Determines which exchange adapter is used to execute the trade through the TradeModule contract.
*/
constructor(
IBaseManager _manager,
IAirdropModule _airdropModule,
ITradeModule _tradeModule,
ExecutionSettings memory _settings
) public BaseExtension(_manager) {
setToken = _manager.setToken();
airdropModule = _airdropModule;
tradeModule = _tradeModule;
settings = _settings;
}
/* ============ External Functions ============ */
/**
* Initializes the extension by:
* 1. Calling initialize on the AirdropModule for the SetToken with required airdrop settings.
* 2. Initializing the TradeModule for the SetToken to allow trading trading with the SetToken.
*/
function initialize() external onlyOperator {
address[] memory tokens = new address[](1);
tokens[0] = R_ETH2;
IAirdropModule.AirdropSettings memory airdropSettings = IAirdropModule.AirdropSettings ({
airdrops: tokens,
feeRecipient: address(setToken),
airdropFee: 0,
anyoneAbsorb: false
});
bytes memory airdropModuleData = abi.encodeWithSelector(airdropModule.initialize.selector, setToken, airdropSettings);
invokeManager(address(airdropModule), airdropModuleData);
bytes memory tradeModuleData = abi.encodeWithSelector(tradeModule.initialize.selector, setToken);
invokeManager(address(tradeModule), tradeModuleData);
}
/**
*
* 1. Absorbs rETH2 into the SetToken
* 2. Trades rETH2 into sETH2 with _minReceivedQuantity
*
* We considered removing the _minReceivedQuantity parameter and storing the slippage parameter as part of
* ExecutionSettings. However, in the event of a black swan event where rETH2 de-pegs, we'd need to updateExecutionSettings
* which would involve a multi-sig txn. Once rETH2 can be redeemed for sETH2 directly, the exchange rate is guaranteed to be at least 1:1.
* Therefore, _minReceiveQuantity can be removed and this function can be made public.
*/
function reinvest(uint256 _minReceiveQuantity) external onlyAllowedCaller(msg.sender) {
bytes memory absorbCallData = abi.encodeWithSelector(
IAirdropModule.absorb.selector,
setToken,
R_ETH2
);
invokeManager(address(airdropModule), absorbCallData);
uint256 rEthUnits = uint256(setToken.getTotalComponentRealUnits(R_ETH2));
require(rEthUnits > 0, "rETH2 units must be greater than zero");
bytes memory tradeCallData = abi.encodeWithSelector(
ITradeModule.trade.selector,
setToken,
settings.exchangeName,
R_ETH2,
rEthUnits,
S_ETH2,
_minReceiveQuantity,
settings.exchangeCallData
);
invokeManager(address(tradeModule), tradeCallData);
}
function updateExecutionSettings(ExecutionSettings memory _settings) external onlyAllowedCaller(msg.sender) {
settings = _settings;
}
}
StreamingFeeSplitExtension.sol 214 lines
/*
Copyright 2021 IndexCooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { TimeLockUpgrade } from "../lib/TimeLockUpgrade.sol";
import { MutualUpgrade } from "../lib/MutualUpgrade.sol";
/**
* @title StreamingFeeSplitExtension
* @author Set Protocol
*
* Smart contract manager extension that allows for splitting and setting streaming fees. Fee splits are updated by operator.
* Any fee updates are timelocked.
*/
contract StreamingFeeSplitExtension is BaseExtension, TimeLockUpgrade, MutualUpgrade {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Events ============ */
event FeesDistributed(
address indexed _operatorFeeRecipient,
address indexed _methodologist,
uint256 _operatorTake,
uint256 _methodologistTake
);
/* ============ State Variables ============ */
ISetToken public setToken;
IStreamingFeeModule public streamingFeeModule;
// Percent of fees in precise units (10^16 = 1%) sent to operator, rest to methodologist
uint256 public operatorFeeSplit;
// Address which receives operator's share of fees when they're distributed. (See IIP-72)
address public operatorFeeRecipient;
/* ============ Constructor ============ */
constructor(
IBaseManager _manager,
IStreamingFeeModule _streamingFeeModule,
uint256 _operatorFeeSplit,
address _operatorFeeRecipient
)
public
BaseExtension(_manager)
{
streamingFeeModule = _streamingFeeModule;
operatorFeeSplit = _operatorFeeSplit;
operatorFeeRecipient = _operatorFeeRecipient;
setToken = manager.setToken();
}
/* ============ External Functions ============ */
/**
* ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual,
* calculates fees for operator and methodologist, and sends to operatorFeeRecipient and methodologist
* respectively.
*/
function accrueFeesAndDistribute() public {
// Emits a FeeActualized event
streamingFeeModule.accrueFee(setToken);
uint256 totalFees = setToken.balanceOf(address(this));
address methodologist = manager.methodologist();
uint256 operatorTake = totalFees.preciseMul(operatorFeeSplit);
uint256 methodologistTake = totalFees.sub(operatorTake);
if (operatorTake > 0) {
setToken.transfer(operatorFeeRecipient, operatorTake);
}
if (methodologistTake > 0) {
setToken.transfer(methodologist, methodologistTake);
}
emit FeesDistributed(operatorFeeRecipient, methodologist, operatorTake, methodologistTake);
}
/**
* MUTUAL UPGRADE: Initializes the streaming fee module. Operator and Methodologist must each call
* this function to execute the update.
*
* This method is called after invoking `replaceProtectedModule` or `emergencyReplaceProtectedModule`
* to configure the replacement streaming fee module's fee settings.
*
* @dev FeeState settings encode the following struct
* ```
* struct FeeState {
* address feeRecipient; // Address to accrue fees to
* uint256 maxStreamingFeePercentage; // Max streaming fee maanager commits to using (1% = 1e16, 100% = 1e18)
* uint256 streamingFeePercentage; // Percent of Set accruing to manager annually (1% = 1e16, 100% = 1e18)
* uint256 lastStreamingFeeTimestamp; // Timestamp last streaming fee was accrued
*}
*```
* @param _settings FeeModule.FeeState settings
*/
function initializeModule(IStreamingFeeModule.FeeState memory _settings)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
bytes memory callData = abi.encodeWithSelector(
IStreamingFeeModule.initialize.selector,
manager.setToken(),
_settings
);
invokeManager(address(streamingFeeModule), callData);
}
/**
* MUTUAL UPGRADE: Updates streaming fee on StreamingFeeModule. Operator and Methodologist must
* each call this function to execute the update. Because the method is timelocked, each party
* must call it twice: once to set the lock and once to execute.
*
* Method is timelocked to protect token owners from sudden changes in fee structure which
* they would rather not bear. The delay gives them a chance to exit their positions without penalty.
*
* NOTE: This will accrue streaming fees though not send to operator fee recipient and methodologist.
*
* @param _newFee Percent of Set accruing to fee extension annually (1% = 1e16, 100% = 1e18)
*/
function updateStreamingFee(uint256 _newFee)
external
mutualUpgrade(manager.operator(), manager.methodologist())
timeLockUpgrade
{
bytes memory callData = abi.encodeWithSelector(
IStreamingFeeModule.updateStreamingFee.selector,
manager.setToken(),
_newFee
);
invokeManager(address(streamingFeeModule), callData);
}
/**
* MUTUAL UPGRADE: Updates fee recipient on streaming fee module.
*
* @param _newFeeRecipient Address of new fee recipient. This should be the address of the fee extension itself.
*/
function updateFeeRecipient(address _newFeeRecipient)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
bytes memory callData = abi.encodeWithSelector(
IStreamingFeeModule.updateFeeRecipient.selector,
manager.setToken(),
_newFeeRecipient
);
invokeManager(address(streamingFeeModule), callData);
}
/**
* MUTUAL UPGRADE: Updates fee split between operator and methodologist. Split defined in precise units (1% = 10^16).
* Fees will be accrued and distributed before the new split goes into effect.
*
* @param _newFeeSplit Percent of fees in precise units (10^16 = 1%) sent to operator, (rest go to the methodologist).
*/
function updateFeeSplit(uint256 _newFeeSplit)
external
mutualUpgrade(manager.operator(), manager.methodologist())
{
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%");
accrueFeesAndDistribute();
operatorFeeSplit = _newFeeSplit;
}
/**
* OPERATOR ONLY: Updates the address that receives the operator's share of the fees (see IIP-72)
*
* @param _newOperatorFeeRecipient Address to send operator's fees to.
*/
function updateOperatorFeeRecipient(address _newOperatorFeeRecipient)
external
onlyOperator
{
require(_newOperatorFeeRecipient != address(0), "Zero address not valid");
operatorFeeRecipient = _newOperatorFeeRecipient;
}
}
TargetWeightWrapExtension.sol 440 lines
/*
Copyright 2024 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
import { Position } from "../lib/Position.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ISetValuer } from "../interfaces/ISetValuer.sol";
import { IWrapModuleV2 } from "../interfaces/IWrapModuleV2.sol";
/**
* @title TargetWeightWrapExtension
* @author Index Coop
* @notice Extension contract for managing asset weights by wrapping a reserve asset into target assets when overweight,
* and unwrapping target assets back into the reserve asset when underweight. Enforces weight bounds during rebalancing.
* @dev Designed for ERC20 reserve assets.
* @dev Designed for wrap and unwrap operations with minimal slippage.
*/
contract TargetWeightWrapExtension is BaseExtension, ReentrancyGuard {
using SafeCast for int256;
using SafeCast for uint256;
using SafeMath for uint256;
using Math for uint256;
using Position for uint256;
using PreciseUnitMath for uint256;
/* ============ Structs ============ */
struct RebalanceInfo {
address reserveAsset; // Address of the reserve asset
uint256 minReserveWeight; // Minimum weight of the reserve asset (100% = 1e18)
uint256 maxReserveWeight; // Maximum weight of the reserve asset (100% = 1e18)
address[] targetAssets; // Array of target assets to wrap/unwrap
}
struct TargetWeightWrapParams {
uint256 minTargetWeight; // Minimum weight of the target asset (100% = 1e18)
uint256 maxTargetWeight; // Maximum weight of the target asset (100% = 1e18)
string wrapAdapterName; // Name of the wrap adapter to use
bytes wrapData; // Data for wrapping
bytes unwrapData; // Data for unwrapping
}
/* ============ Events ============ */
event RebalanceAccessUpdated(bool isRebalanceOpen);
event TargetsSet(
address indexed reserveAsset,
uint256 minReserveWeight,
uint256 maxReserveWeight,
address[] targetAssets,
TargetWeightWrapParams[] executionParams
);
event RebalancePaused();
event WrapModuleUpdated(address indexed wrapModule);
event SetValuerUpdated(address indexed setValuer);
/* ========== Immutables ========= */
ISetToken public immutable setToken;
/* ========== State Variables ========= */
IWrapModuleV2 public wrapModule;
ISetValuer public setValuer;
// Flag indicating if target weights are set and wrap() and unwrap() can potentially be called
bool public isRebalancingActive;
// Flag indicating if wrap() and unwrap() can be called by anyone or only the operator
bool public isRebalanceOpen;
// Mapping of target assets to their target weight wrap() and unwrap() execution parameters
mapping(address => TargetWeightWrapParams) public executionParams;
// Reserve asset execution parameters and target assets to rebalance
RebalanceInfo public rebalanceInfo;
/* ============ Modifiers ============ */
modifier onlyAllowedRebalancer() {
_validateOnlyAllowedRebalancer();
_;
}
/* ============ Constructor ============ */
/**
* @notice Initializes the extension with the required contracts and parameters.
* @param _manager Address of Index Manager contract
* @param _wrapModule Address of IWrapModuleV2 for wrapping and unwrapping reserve asset
* @param _setValuer Address of SetValuer for calculating valuations and weights
* @param _isRebalanceOpen Flag indicating if anyone can rebalance
*/
constructor(
IBaseManager _manager,
IWrapModuleV2 _wrapModule,
ISetValuer _setValuer,
bool _isRebalanceOpen
) public BaseExtension(_manager) {
manager = _manager;
wrapModule = _wrapModule;
setValuer = _setValuer;
isRebalanceOpen = _isRebalanceOpen;
ISetToken setToken_ = manager.setToken();
setToken = setToken_;
_setWrapModule(setToken_, _wrapModule);
_setSetValuer(setToken_, _setValuer);
}
/* ========== Rebalance Functions ========== */
/**
* @notice Wraps reserve asset units into the target asset.
* @dev Must be called when the reserve asset is overweight, rebalancing is enabled, and the caller is an allowed rebalancer.
* Ensures that after wrapping, the target asset does not become overweight and the reserve asset does not become underweight.
* @param _targetAsset Address of the target asset to wrap into.
* @param _reserveUnits Units of the reserve asset to wrap.
*/
function wrap(
address _targetAsset,
uint256 _reserveUnits
)
external
nonReentrant
onlyAllowedRebalancer
{
require(isRebalancingActive, "Rebalancing is not active");
require(rebalanceInfo.targetAssets.contains(_targetAsset), "Invalid target asset");
bytes memory data = abi.encodeWithSelector(
wrapModule.wrap.selector,
setToken,
rebalanceInfo.reserveAsset,
_targetAsset,
_reserveUnits,
executionParams[_targetAsset].wrapAdapterName,
executionParams[_targetAsset].wrapData
);
invokeManager(address(wrapModule), data);
(uint256 targetAssetWeight, uint256 reserveWeight) = getTargetAssetAndReserveWeight(_targetAsset);
require(targetAssetWeight <= executionParams[_targetAsset].maxTargetWeight, "Target asset overweight post-wrap");
require(reserveWeight >= rebalanceInfo.minReserveWeight, "Reserve asset underweight post-wrap");
}
/**
* @notice Unwraps target asset units into the reserve asset.
* @dev Must be called when the reserve asset is underweight, rebalancing is enabled, and the caller is an allowed rebalancer.
* Ensures that after unwrapping, the target asset does not become underweight and the reserve asset does not become overweight.
* @param _targetAsset Address of target asset to unwrap from
* @param _targetUnits Units of target asset to unwrap
*/
function unwrap(
address _targetAsset,
uint256 _targetUnits
)
external
nonReentrant
onlyAllowedRebalancer
{
require(isRebalancingActive, "Rebalancing is not active");
require(rebalanceInfo.targetAssets.contains(_targetAsset), "Invalid target asset");
require(isReserveUnderweight(), "Reserve asset is not underweight");
bytes memory data = abi.encodeWithSelector(
wrapModule.unwrap.selector,
setToken,
rebalanceInfo.reserveAsset,
_targetAsset,
_targetUnits,
executionParams[_targetAsset].wrapAdapterName,
executionParams[_targetAsset].unwrapData
);
invokeManager(address(wrapModule), data);
(uint256 targetAssetWeight, uint256 reserveWeight) = getTargetAssetAndReserveWeight(_targetAsset);
require(targetAssetWeight >= executionParams[_targetAsset].minTargetWeight, "Target asset underweight post-unwrap");
require(reserveWeight <= rebalanceInfo.maxReserveWeight, "Reserve asset overweight post-unwrap");
}
/* ========== Operator Functions ========== */
/**
* @notice Sets the reserve asset, target assets, and their associated execution parameters.
* @dev Only callable by the operator.
* @dev The weights are percentages where 100% equals 1e18.
* @param _reserveAsset Address of the reserve asset.
* @param _minReserveWeight Minimum allowable weight of the reserve asset.
* @param _maxReserveWeight Maximum allowable weight of the reserve asset.
* @param _targetAssets Array of target asset addresses.
* @param _executionParams Array of execution parameters corresponding to each target asset.
*/
function setTargetWeights(
address _reserveAsset,
uint256 _minReserveWeight,
uint256 _maxReserveWeight,
address[] memory _targetAssets,
TargetWeightWrapParams[] memory _executionParams
)
external
onlyOperator
{
require(_targetAssets.length == _executionParams.length, "Mismatched array lengths");
require(_minReserveWeight <= _maxReserveWeight, "Invalid min reserve weight");
require(_maxReserveWeight <= PreciseUnitMath.preciseUnit(), "Invalid max reserve weight");
rebalanceInfo = RebalanceInfo({
reserveAsset: _reserveAsset,
minReserveWeight: _minReserveWeight,
maxReserveWeight: _maxReserveWeight,
targetAssets: _targetAssets
});
for (uint256 i = 0; i < _targetAssets.length; i++) {
require(_executionParams[i].minTargetWeight <= _executionParams[i].maxTargetWeight, "Invalid min target weight");
require(_executionParams[i].maxTargetWeight <= PreciseUnitMath.preciseUnit(), "Invalid max target weight");
executionParams[_targetAssets[i]] = _executionParams[i];
}
isRebalancingActive = true;
emit TargetsSet(_reserveAsset, _minReserveWeight, _maxReserveWeight, _targetAssets, _executionParams);
}
/**
* @notice Pauses rebalancing until targets are reconfigured.
* @dev Only callable by the operator.
*/
function pauseRebalance() external onlyOperator {
isRebalancingActive = false;
emit RebalancePaused();
}
/**
* @notice Sets the flag to open or restrict rebalancing access through this extension.
* @dev This function can only be called by the operator.
* @param _isRebalanceOpen Flag to indicate if rebalancing is open to anyone.
*/
function setIsRebalanceOpen(bool _isRebalanceOpen) external onlyOperator {
isRebalanceOpen = _isRebalanceOpen;
emit RebalanceAccessUpdated(_isRebalanceOpen);
}
/**
* @notice Sets the WrapModule contract used for wrapping and unwrapping assets.
* @dev This function can only be called by the operator.
* @param _wrapModule Address of the WrapModuleV2 contract.
*/
function setWrapModule(IWrapModuleV2 _wrapModule) external onlyOperator {
_setWrapModule(setToken, _wrapModule);
}
/**
* @notice Sets the SetValuer contract used for calculating valuations and weights.
* @dev This function can only be called by the operator.
* @param _setValuer Address of the SetValuer contract.
*/
function setSetValuer(ISetValuer _setValuer) external onlyOperator {
_setSetValuer(setToken, _setValuer);
}
/**
* @notice Initializes the Set Token within the Wrap Module.
* @dev This function can only be called by the operator.
*/
function initialize() external onlyOperator {
bytes memory data = abi.encodeWithSelector(wrapModule.initialize.selector, setToken);
invokeManager(address(wrapModule), data);
}
/* ========== External Getters ========== */
/**
* @notice Gets the valuation of the reserve asset.
* @return reserveValuation The valuation of the reserve asset.
*/
function getReserveValuation() public view returns(uint256 reserveValuation) {
reserveValuation = setToken.isComponent(rebalanceInfo.reserveAsset)
? setValuer.calculateComponentValuation(setToken, rebalanceInfo.reserveAsset, rebalanceInfo.reserveAsset)
: 0;
}
/**
* @notice Gets the valuation of a specific target asset.
* @param _targetAsset The address of the target asset.
* @return targetAssetValuation The valuation of the specified target asset.
*/
function getTargetAssetValuation(address _targetAsset) public view returns(uint256 targetAssetValuation) {
targetAssetValuation = setToken.isComponent(_targetAsset)
? setValuer.calculateComponentValuation(setToken, _targetAsset, rebalanceInfo.reserveAsset)
: 0;
}
/**
* @notice Gets the total valuation of the SetToken.
* @return totalValuation The total valuation of the SetToken.
*/
function getTotalValuation() public view returns(uint256 totalValuation) {
totalValuation = setValuer.calculateSetTokenValuation(setToken, rebalanceInfo.reserveAsset);
}
/**
* @notice Gets the weight of the reserve asset relative to the total valuation of the SetToken.
* @dev The weight is returned as a percentage where 100% equals 1e18.
* @return reserveWeight The weight of the reserve asset relative to the SetToken's total valuation.
*/
function getReserveWeight() public view returns(uint256 reserveWeight) {
uint256 reserveValuation = getReserveValuation();
uint256 totalValuation = getTotalValuation();
reserveWeight = reserveValuation.preciseDiv(totalValuation);
}
/**
* @notice Gets the weight of a specific target asset relative to the total valuation of the SetToken.
* @dev The weight is returned as a percentage where 100% equals 1e18.
* @param _targetAsset The address of the target asset.
* @return targetAssetWeight The weight of the specified target asset relative to the SetToken's total valuation.
*/
function getTargetAssetWeight(address _targetAsset) public view returns(uint256 targetAssetWeight) {
uint256 targetAssetValuation = getTargetAssetValuation(_targetAsset);
uint256 totalValuation = getTotalValuation();
targetAssetWeight = targetAssetValuation.preciseDiv(totalValuation);
}
/**
* @notice Gets the weights of both the target asset and the reserve asset relative to the total valuation of the SetToken.
* @dev The weights are returned as percentages where 100% equals 1e18.
* @param _targetAsset The address of the target asset.
* @return targetAssetWeight The weight of the target asset relative to the SetToken's total valuation.
* @return reserveWeight The weight of the reserve asset relative to the SetToken's total valuation.
*/
function getTargetAssetAndReserveWeight(address _targetAsset) public view returns(uint256 targetAssetWeight, uint256 reserveWeight) {
uint256 targetAssetValuation = getTargetAssetValuation(_targetAsset);
uint256 reserveValuation = getReserveValuation();
uint256 totalValuation = getTotalValuation();
targetAssetWeight = targetAssetValuation.preciseDiv(totalValuation);
reserveWeight = reserveValuation.preciseDiv(totalValuation);
}
/**
* @notice Checks if the reserve asset is overweight.
*/
function isReserveOverweight() public view returns(bool) {
return getReserveWeight() > rebalanceInfo.maxReserveWeight;
}
/**
* @notice Checks if the reserve asset is underweight.
*/
function isReserveUnderweight() public view returns(bool) {
return getReserveWeight() < rebalanceInfo.minReserveWeight;
}
/**
* @notice Checks if the target asset is overweight.
* @param _targetAsset The address of the target asset.
*/
function isTargetOverweight(address _targetAsset) public view returns(bool) {
return getTargetAssetWeight(_targetAsset) > executionParams[_targetAsset].maxTargetWeight;
}
/**
* @notice Checks if the target asset is underweight.
* @param _targetAsset The address of the target asset.
*/
function isTargetUnderweight(address _targetAsset) public view returns(bool) {
return getTargetAssetWeight(_targetAsset) < executionParams[_targetAsset].minTargetWeight;
}
/**
* @notice Gets the list of target assets that can be wrapped into or unwrapped from during rebalancing.
* @return An array of addresses representing the target assets.
*/
function getTargetAssets() external view returns(address[] memory) {
return rebalanceInfo.targetAssets;
}
/* ========== Internal Functions ========== */
/**
* Sets the WrapModuleV2 contract used for wrapping and unwrapping assets.
* @param _setToken Address of the SetToken contract.
* @param _wrapModule Address of the WrapModuleV2 contract.
*/
function _setWrapModule(ISetToken _setToken, IWrapModuleV2 _wrapModule) internal {
require(_setToken.moduleStates(address(_wrapModule)) == ISetToken.ModuleState.PENDING, "WrapModuleV2 not pending");
wrapModule = _wrapModule;
emit WrapModuleUpdated(address(_wrapModule));
}
/**
* Sets the SetValuer contract used for calculating valuations and weights.
* @param _setToken Address of the SetToken contract.
* @param _setValuer Address of the SetValuer contract.
*/
function _setSetValuer(ISetToken _setToken, ISetValuer _setValuer) internal {
require(IController(_setToken.controller()).isResource(address(_setValuer)), "SetValuer not approved by controller");
setValuer = _setValuer;
emit SetValuerUpdated(address(_setValuer));
}
/* ============== Modifier Helpers ===============
* Internal functions used to reduce bytecode size
*/
/*
* Caller must be oeprator if isRebalanceOpen is false
*/
function _validateOnlyAllowedRebalancer() internal {
if (!isRebalanceOpen) {
require(msg.sender == manager.operator(), "Must be allowed rebalancer");
}
}
}
WrapExtension.sol 171 lines
/*
Copyright 2021 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWrapModule } from "../interfaces/IWrapModule.sol";
/**
* @title WrapExtension
* @author Index Coop
*
* Manager extension for interacting with WrapModule
*/
contract WrapExtension is BaseExtension {
/* ========== State Variables ========= */
// Address of Set Token
ISetToken public immutable setToken;
// Address of WrapModule
IWrapModule public immutable wrapModule;
/* ============ Constructor ============ */
/**
* Sets state variables
*
* @param _manager Manager contract
* @param _wrapModule Set Protocol WrapModule
*/
constructor(IBaseManager _manager, IWrapModule _wrapModule) public BaseExtension(_manager) {
manager = _manager;
setToken = manager.setToken();
wrapModule = _wrapModule;
}
/* ========== External Functions ========== */
/**
* OPERATOR ONLY: Initializes the Set Token on the Wrap Module.
*/
function initialize() external onlyOperator {
bytes memory data = abi.encodeWithSelector(wrapModule.initialize.selector, setToken);
invokeManager(address(wrapModule), data);
}
/**
* OPERATOR ONLY: Calls wrap on the WrapModule.
*
* @param _underlyingToken address of underlying token
* @param _wrappedToken address of wrapped token
* @param _underlyingUnits units of underlying to wrap
* @param _integrationName Set Protocol integreation name for the wrap adapter
*/
function wrap(
address _underlyingToken,
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName
)
external
onlyOperator
{
bytes memory data = abi.encodeWithSelector(
wrapModule.wrap.selector,
setToken,
_underlyingToken,
_wrappedToken,
_underlyingUnits,
_integrationName
);
invokeManager(address(wrapModule), data);
}
/**
* OPERATOR ONLY: Calls wrapWithEther on the WrapModule.
*
* @param _wrappedToken address of wrapped token
* @param _underlyingUnits units of weth to wrap
* @param _integrationName Set Protocol integreation name for the wrap adapter
*/
function wrapWithEther(
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName
)
external
onlyOperator
{
bytes memory data = abi.encodeWithSelector(
wrapModule.wrapWithEther.selector,
setToken,
_wrappedToken,
_underlyingUnits,
_integrationName
);
invokeManager(address(wrapModule), data);
}
/**
* OPERATOR ONLY: Calls unwrap on the WrapModule.
*
* @param _underlyingToken address of underlying token
* @param _wrappedToken address of wrapped token
* @param _wrappedUnits units of wrapped token to unwrap
* @param _integrationName Set Protocol integreation name for the wrap adapter
*/
function unwrap(
address _underlyingToken,
address _wrappedToken,
uint256 _wrappedUnits,
string calldata _integrationName
)
external
onlyOperator
{
bytes memory data = abi.encodeWithSelector(
wrapModule.unwrap.selector,
setToken,
_underlyingToken,
_wrappedToken,
_wrappedUnits,
_integrationName
);
invokeManager(address(wrapModule), data);
}
/**
* OPERATOR ONLY: Calls unwrapWithEther on the WrapModule.
*
* @param _wrappedToken address of wrapped token
* @param _wrappedUnits units of wrapped token to unwrap
* @param _integrationName Set Protocol integreation name for the wrap adapter
*/
function unwrapWithEther(
address _wrappedToken,
uint256 _wrappedUnits,
string calldata _integrationName
)
external
onlyOperator
{
bytes memory data = abi.encodeWithSelector(
wrapModule.unwrapWithEther.selector,
setToken,
_wrappedToken,
_wrappedUnits,
_integrationName
);
invokeManager(address(wrapModule), data);
}
}
DEXAdapter.sol 821 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ICurveCalculator } from "../interfaces/external/ICurveCalculator.sol";
import { ICurveAddressProvider } from "../interfaces/external/ICurveAddressProvider.sol";
import { ICurvePoolRegistry } from "../interfaces/external/ICurvePoolRegistry.sol";
import { ICurvePool } from "../interfaces/external/ICurvePool.sol";
import { ISwapRouter02 } from "../interfaces/external/ISwapRouter02.sol";
import { IQuoter } from "../interfaces/IQuoter.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title DEXAdapter
* @author Index Coop
*
* Adapter to execute swaps on different DEXes
*/
library DEXAdapter {
using SafeERC20 for IERC20;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ Enums ============ */
enum Exchange { None, Quickswap, Sushiswap, UniV3, Curve }
/* ============ Structs ============ */
struct Addresses {
address quickRouter;
address sushiRouter;
address uniV3Router;
address uniV3Quoter;
address curveAddressProvider;
address curveCalculator;
// Wrapped native token (WMATIC on polygon)
address weth;
}
struct SwapData {
address[] path;
uint24[] fees;
address pool;
Exchange exchange;
}
struct CurvePoolData {
int128 nCoins;
uint256[8] balances;
uint256 A;
uint256 fee;
uint256[8] rates;
uint256[8] decimals;
}
/**
* Swap exact tokens for another token on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _swapData Swap data containing the path and fee levels (latter only used for uniV3)
*
* @return amountOut The amount of output tokens
*/
function swapExactTokensForTokens(
Addresses memory _addresses,
uint256 _amountIn,
uint256 _minAmountOut,
SwapData memory _swapData
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountIn;
}
if(_swapData.exchange == Exchange.Curve){
return _swapExactTokensForTokensCurve(
_swapData.path,
_swapData.pool,
_amountIn,
_minAmountOut,
_addresses
);
}
if(_swapData.exchange== Exchange.UniV3){
return _swapExactTokensForTokensUniV3(
_swapData.path,
_swapData.fees,
_amountIn,
_minAmountOut,
ISwapRouter02(_addresses.uniV3Router)
);
} else {
return _swapExactTokensForTokensUniV2(
_swapData.path,
_amountIn,
_minAmountOut,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Swap tokens for exact amount of output tokens on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _swapData Swap data containing the path and fee levels (latter only used for uniV3)
*
* @return amountIn The amount of input tokens spent
*/
function swapTokensForExactTokens(
Addresses memory _addresses,
uint256 _amountOut,
uint256 _maxAmountIn,
SwapData memory _swapData
)
external
returns (uint256 amountIn)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountOut;
}
if(_swapData.exchange == Exchange.Curve){
return _swapTokensForExactTokensCurve(
_swapData.path,
_swapData.pool,
_amountOut,
_maxAmountIn,
_addresses
);
}
if(_swapData.exchange == Exchange.UniV3){
return _swapTokensForExactTokensUniV3(
_swapData.path,
_swapData.fees,
_amountOut,
_maxAmountIn,
ISwapRouter02(_addresses.uniV3Router)
);
} else {
return _swapTokensForExactTokensUniV2(
_swapData.path,
_amountOut,
_maxAmountIn,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Gets the output amount of a token swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function getAmountOut(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountIn
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountIn;
}
if (_swapData.exchange == Exchange.UniV3) {
return _getAmountOutUniV3(_swapData, _addresses.uniV3Quoter, _amountIn);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountOutCurve(_swapData.pool, i, j, _amountIn, _addresses);
} else {
return _getAmountOutUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountIn
);
}
}
/**
* Gets the input amount of a fixed output swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function getAmountIn(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountOut
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountOut;
}
if (_swapData.exchange == Exchange.UniV3) {
return _getAmountInUniV3(_swapData, _addresses.uniV3Quoter, _amountOut);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountInCurve(_swapData.pool, i, j, _amountOut, _addresses);
} else {
return _getAmountInUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountOut
);
}
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/* ============ Private Methods ============ */
/**
* Execute exact output swap via a UniV2 based DEX. (such as sushiswap);
*
* @param _path List of token address to swap via.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _router Address of the uniV2 router to use
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV2(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _maxAmountIn);
return _router.swapTokensForExactTokens(_amountOut, _maxAmountIn, _path, address(this), block.timestamp)[0];
}
/**
* Execute exact output swap via UniswapV3
*
* @param _path List of token address to swap via. (In the order as
* expected by uniV2, the first element being the input toen)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountOut,
uint256 _maxAmountIn,
ISwapRouter02 _uniV3Router
)
private
returns(uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _maxAmountIn);
if(_path.length == 2){
ISwapRouter02.ExactOutputSingleParams memory params =
ISwapRouter02.ExactOutputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactOutputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, true);
ISwapRouter02.ExactOutputParams memory params =
ISwapRouter02.ExactOutputParams({
path: pathV3,
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn
});
return _uniV3Router.exactOutput(params);
}
}
/**
* Execute exact input swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
if(_path[0] == ETH_ADDRESS){
IWETH(_addresses.weth).withdraw(_amountIn);
}
amountOut = _exchangeCurve(i, j, _pool, _amountIn, _minAmountOut, _path[0]);
if(_path[_path.length-1] == ETH_ADDRESS){
IWETH(_addresses.weth).deposit{value: amountOut}();
}
}
/**
* Execute exact output swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
*
* @return amountOut The amount of output token obtained
*/
function _swapTokensForExactTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountOut,
uint256 _maxAmountIn,
Addresses memory _addresses
)
private
returns (uint256)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
if(_path[0] == ETH_ADDRESS){
IWETH(_addresses.weth).withdraw(_maxAmountIn);
}
uint256 returnedAmountOut = _exchangeCurve(i, j, _pool, _maxAmountIn, _amountOut, _path[0]);
require(_amountOut <= returnedAmountOut, "ExchangeIssuance: CURVE_UNDERBOUGHT");
uint256 swappedBackAmountIn;
if(returnedAmountOut > _amountOut){
swappedBackAmountIn = _exchangeCurve(j, i, _pool, returnedAmountOut.sub(_amountOut), 0, _path[1]);
if(_path[0] == ETH_ADDRESS){
IWETH(_addresses.weth).deposit{ value: swappedBackAmountIn }();
}
}
if(_path[_path.length-1] == ETH_ADDRESS){
IWETH(_addresses.weth).deposit{ value: _amountOut }();
}
return _maxAmountIn.sub(swappedBackAmountIn);
}
function _exchangeCurve(
int128 _i,
int128 _j,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
address _from
)
private
returns (uint256 amountOut)
{
ICurvePool pool = ICurvePool(_pool);
if(_from == ETH_ADDRESS){
amountOut = pool.exchange{value: _amountIn}(
_i,
_j,
_amountIn,
_minAmountOut
);
}
else {
_safeApprove(IERC20(_from), _pool, _amountIn);
amountOut = pool.exchange(
_i,
_j,
_amountIn,
_minAmountOut
);
}
}
/**
* Calculate required input amount to get a given output amount via Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountInCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountOut,
Addresses memory _addresses
)
private
view
returns (uint256)
{
CurvePoolData memory poolData = _getCurvePoolData(_pool, ICurveAddressProvider(_addresses.curveAddressProvider));
return ICurveCalculator(_addresses.curveCalculator).get_dx(
poolData.nCoins,
poolData.balances,
poolData.A,
poolData.fee,
poolData.rates,
poolData.decimals,
false,
_i,
_j,
_amountOut
) + ROUNDING_ERROR_MARGIN;
}
/**
* Calculate output amount of a Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountIn The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountOutCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountIn,
Addresses memory _addresses
)
private
view
returns (uint256)
{
return ICurvePool(_pool).get_dy(_i, _j, _amountIn);
}
/**
* Get metadata on curve pool required to calculate input amount from output amount
*
* @param _pool Address of curve pool to use
* @param _curveAddressProvider Address of curve address provider
*
* @return Struct containing all required data to perform getAmountInCurve calculation
*/
function _getCurvePoolData(
address _pool,
ICurveAddressProvider _curveAddressProvider
) private view returns(CurvePoolData memory)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
return CurvePoolData(
int128(registry.get_n_coins(_pool)[0]),
registry.get_balances(_pool),
registry.get_A(_pool),
registry.get_fees(_pool)[0],
registry.get_rates(_pool),
registry.get_decimals(_pool)
);
}
/**
* Get token indices for given pool
* NOTE: This was necessary sine the get_coin_indices function of the CurvePoolRegistry did not work for StEth/ETH pool
*
* @param _pool Address of curve pool to use
* @param _from Address of input token
* @param _to Address of output token
* @param _curveAddressProvider Address of curve address provider
*
* @return i Index of input token
* @return j Index of output token
*/
function _getCoinIndices(
address _pool,
address _from,
address _to,
ICurveAddressProvider _curveAddressProvider
)
private
view
returns (int128 i, int128 j)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
// Set to out of range index to signal the coin is not found yet
i = 9;
j = 9;
address[8] memory poolCoins = registry.get_coins(_pool);
for(uint256 k = 0; k < 8; k++){
if(poolCoins[k] == _from){
i = int128(k);
}
else if(poolCoins[k] == _to){
j = int128(k);
}
// ZeroAddress signals end of list
if(poolCoins[k] == address(0) || (i != 9 && j != 9)){
break;
}
}
require(i != 9, "ExchangeIssuance: CURVE_FROM_NOT_FOUND");
require(j != 9, "ExchangeIssuance: CURVE_TO_NOT_FOUND");
return (i, j);
}
/**
* Execute exact input swap via UniswapV3
*
* @param _path List of token address to swap via.
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountIn,
uint256 _minAmountOut,
ISwapRouter02 _uniV3Router
)
private
returns (uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _amountIn);
if(_path.length == 2){
ISwapRouter02.ExactInputSingleParams memory params =
ISwapRouter02.ExactInputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactInputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, false);
ISwapRouter02.ExactInputParams memory params =
ISwapRouter02.ExactInputParams({
path: pathV3,
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut
});
uint amountOut = _uniV3Router.exactInput(params);
return amountOut;
}
}
/**
* Execute exact input swap via UniswapV2
*
* @param _path List of token address to swap via.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _router Address of uniV2 router to use
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV2(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _amountIn);
// NOTE: The following was changed from always returning result at position [1] to returning the last element of the result array
// With this change, the actual output is correctly returned also for multi-hop swaps
// See https://github.com/IndexCoop/index-coop-smart-contracts/pull/116
uint256[] memory result = _router.swapExactTokensForTokens(_amountIn, _minAmountOut, _path, address(this), block.timestamp);
// result = uint[] memory The input token amount and all subsequent output token amounts.
// we are usually only interested in the actual amount of the output token (so result element at the last place)
return result[result.length-1];
}
/**
* Gets the output amount of a token swap on Uniswap V2
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountIn
)
private
view
returns (uint256)
{
return _router.getAmountsOut(_amountIn, _swapData.path)[_swapData.path.length-1];
}
/**
* Gets the input amount of a fixed output swap on Uniswap V2.
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountOut
)
private
view
returns (uint256)
{
return _router.getAmountsIn(_amountOut, _swapData.path)[0];
}
/**
* Gets the output amount of a token swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter the uniswap v3 quoter
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountIn
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, false);
return IQuoter(_quoter).quoteExactInput(path, _amountIn);
}
/**
* Gets the input amount of a fixed output swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter uniswap v3 quoter
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountOut
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, true);
return IQuoter(_quoter).quoteExactOutput(path, _amountOut);
}
/**
* Encode path / fees to bytes in the format expected by UniV3 router
*
* @param _path List of token address to swap via (starting with input token)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _reverseOrder Boolean indicating if path needs to be reversed to start with output token.
* (which is the case for exact output swap)
*
* @return encodedPath Encoded path to be forwared to uniV3 router
*/
function _encodePathV3(
address[] memory _path,
uint24[] memory _fees,
bool _reverseOrder
)
private
pure
returns(bytes memory encodedPath)
{
if(_reverseOrder){
encodedPath = abi.encodePacked(_path[_path.length-1]);
for(uint i = 0; i < _fees.length; i++){
uint index = _fees.length - i - 1;
encodedPath = abi.encodePacked(encodedPath, _fees[index], _path[index]);
}
} else {
encodedPath = abi.encodePacked(_path[0]);
for(uint i = 0; i < _fees.length; i++){
encodedPath = abi.encodePacked(encodedPath, _fees[i], _path[i+1]);
}
}
}
function _getRouter(
Exchange _exchange,
Addresses memory _addresses
)
private
pure
returns (IUniswapV2Router02)
{
return IUniswapV2Router02(
(_exchange == Exchange.Quickswap) ? _addresses.quickRouter : _addresses.sushiRouter
);
}
}
DEXAdapterV2.sol 802 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ICurveCalculator } from "../interfaces/external/ICurveCalculator.sol";
import { ICurveAddressProvider } from "../interfaces/external/ICurveAddressProvider.sol";
import { ICurvePoolRegistry } from "../interfaces/external/ICurvePoolRegistry.sol";
import { ICurvePool } from "../interfaces/external/ICurvePool.sol";
import { ISwapRouter02 } from "../interfaces/external/ISwapRouter02.sol";
import { IQuoter } from "../interfaces/IQuoter.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title DEXAdapterV2
* @author Index Coop
*
* Same as DEXAdapter but without automatic WETH deposit / withdraw
*/
library DEXAdapterV2 {
using SafeERC20 for IERC20;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ Enums ============ */
enum Exchange { None, Quickswap, Sushiswap, UniV3, Curve }
/* ============ Structs ============ */
struct Addresses {
address quickRouter;
address sushiRouter;
address uniV3Router;
address uniV3Quoter;
address curveAddressProvider;
address curveCalculator;
// Wrapped native token (WMATIC on polygon)
address weth;
}
struct SwapData {
address[] path;
uint24[] fees;
address pool;
Exchange exchange;
}
struct CurvePoolData {
int128 nCoins;
uint256[8] balances;
uint256 A;
uint256 fee;
uint256[8] rates;
uint256[8] decimals;
}
/**
* Swap exact tokens for another token on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _swapData Swap data containing the path and fee levels (latter only used for uniV3)
*
* @return amountOut The amount of output tokens
*/
function swapExactTokensForTokens(
Addresses memory _addresses,
uint256 _amountIn,
uint256 _minAmountOut,
SwapData memory _swapData
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountIn;
}
if(_swapData.exchange == Exchange.Curve){
return _swapExactTokensForTokensCurve(
_swapData.path,
_swapData.pool,
_amountIn,
_minAmountOut,
_addresses
);
}
if(_swapData.exchange== Exchange.UniV3){
return _swapExactTokensForTokensUniV3(
_swapData.path,
_swapData.fees,
_amountIn,
_minAmountOut,
ISwapRouter02(_addresses.uniV3Router)
);
} else {
return _swapExactTokensForTokensUniV2(
_swapData.path,
_amountIn,
_minAmountOut,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Swap tokens for exact amount of output tokens on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _swapData Swap data containing the path and fee levels (latter only used for uniV3)
*
* @return amountIn The amount of input tokens spent
*/
function swapTokensForExactTokens(
Addresses memory _addresses,
uint256 _amountOut,
uint256 _maxAmountIn,
SwapData memory _swapData
)
external
returns (uint256 amountIn)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountOut;
}
if(_swapData.exchange == Exchange.Curve){
return _swapTokensForExactTokensCurve(
_swapData.path,
_swapData.pool,
_amountOut,
_maxAmountIn,
_addresses
);
}
if(_swapData.exchange == Exchange.UniV3){
return _swapTokensForExactTokensUniV3(
_swapData.path,
_swapData.fees,
_amountOut,
_maxAmountIn,
ISwapRouter02(_addresses.uniV3Router)
);
} else {
return _swapTokensForExactTokensUniV2(
_swapData.path,
_amountOut,
_maxAmountIn,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Gets the output amount of a token swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function getAmountOut(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountIn
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountIn;
}
if (_swapData.exchange == Exchange.UniV3) {
return _getAmountOutUniV3(_swapData, _addresses.uniV3Quoter, _amountIn);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountOutCurve(_swapData.pool, i, j, _amountIn, _addresses);
} else {
return _getAmountOutUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountIn
);
}
}
/**
* Gets the input amount of a fixed output swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function getAmountIn(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountOut
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountOut;
}
if (_swapData.exchange == Exchange.UniV3) {
return _getAmountInUniV3(_swapData, _addresses.uniV3Quoter, _amountOut);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountInCurve(_swapData.pool, i, j, _amountOut, _addresses);
} else {
return _getAmountInUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountOut
);
}
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/* ============ Private Methods ============ */
/**
* Execute exact output swap via a UniV2 based DEX. (such as sushiswap);
*
* @param _path List of token address to swap via.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _router Address of the uniV2 router to use
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV2(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _maxAmountIn);
return _router.swapTokensForExactTokens(_amountOut, _maxAmountIn, _path, address(this), block.timestamp)[0];
}
/**
* Execute exact output swap via UniswapV3
*
* @param _path List of token address to swap via. (In the order as
* expected by uniV2, the first element being the input toen)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountOut,
uint256 _maxAmountIn,
ISwapRouter02 _uniV3Router
)
private
returns(uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _maxAmountIn);
if(_path.length == 2){
ISwapRouter02.ExactOutputSingleParams memory params =
ISwapRouter02.ExactOutputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactOutputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, true);
ISwapRouter02.ExactOutputParams memory params =
ISwapRouter02.ExactOutputParams({
path: pathV3,
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn
});
return _uniV3Router.exactOutput(params);
}
}
/**
* Execute exact input swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
amountOut = _exchangeCurve(i, j, _pool, _amountIn, _minAmountOut, _path[0]);
}
/**
* Execute exact output swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
*
* @return amountOut The amount of output token obtained
*/
function _swapTokensForExactTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountOut,
uint256 _maxAmountIn,
Addresses memory _addresses
)
private
returns (uint256)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
uint256 returnedAmountOut = _exchangeCurve(i, j, _pool, _maxAmountIn, _amountOut, _path[0]);
require(_amountOut <= returnedAmountOut, "ExchangeIssuance: CURVE_UNDERBOUGHT");
uint256 swappedBackAmountIn;
if(returnedAmountOut > _amountOut){
swappedBackAmountIn = _exchangeCurve(j, i, _pool, returnedAmountOut.sub(_amountOut), 0, _path[1]);
}
return _maxAmountIn.sub(swappedBackAmountIn);
}
function _exchangeCurve(
int128 _i,
int128 _j,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
address _from
)
private
returns (uint256 amountOut)
{
ICurvePool pool = ICurvePool(_pool);
if(_from == ETH_ADDRESS){
amountOut = pool.exchange{value: _amountIn}(
_i,
_j,
_amountIn,
_minAmountOut
);
}
else {
IERC20(_from).approve(_pool, _amountIn);
amountOut = pool.exchange(
_i,
_j,
_amountIn,
_minAmountOut
);
}
}
/**
* Calculate required input amount to get a given output amount via Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountInCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountOut,
Addresses memory _addresses
)
private
view
returns (uint256)
{
CurvePoolData memory poolData = _getCurvePoolData(_pool, ICurveAddressProvider(_addresses.curveAddressProvider));
return ICurveCalculator(_addresses.curveCalculator).get_dx(
poolData.nCoins,
poolData.balances,
poolData.A,
poolData.fee,
poolData.rates,
poolData.decimals,
false,
_i,
_j,
_amountOut
) + ROUNDING_ERROR_MARGIN;
}
/**
* Calculate output amount of a Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountIn The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountOutCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountIn,
Addresses memory _addresses
)
private
view
returns (uint256)
{
return ICurvePool(_pool).get_dy(_i, _j, _amountIn);
}
/**
* Get metadata on curve pool required to calculate input amount from output amount
*
* @param _pool Address of curve pool to use
* @param _curveAddressProvider Address of curve address provider
*
* @return Struct containing all required data to perform getAmountInCurve calculation
*/
function _getCurvePoolData(
address _pool,
ICurveAddressProvider _curveAddressProvider
) private view returns(CurvePoolData memory)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
return CurvePoolData(
int128(registry.get_n_coins(_pool)[0]),
registry.get_balances(_pool),
registry.get_A(_pool),
registry.get_fees(_pool)[0],
registry.get_rates(_pool),
registry.get_decimals(_pool)
);
}
/**
* Get token indices for given pool
* NOTE: This was necessary sine the get_coin_indices function of the CurvePoolRegistry did not work for StEth/ETH pool
*
* @param _pool Address of curve pool to use
* @param _from Address of input token
* @param _to Address of output token
* @param _curveAddressProvider Address of curve address provider
*
* @return i Index of input token
* @return j Index of output token
*/
function _getCoinIndices(
address _pool,
address _from,
address _to,
ICurveAddressProvider _curveAddressProvider
)
private
view
returns (int128 i, int128 j)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
// Set to out of range index to signal the coin is not found yet
i = 9;
j = 9;
address[8] memory poolCoins = registry.get_coins(_pool);
for(uint256 k = 0; k < 8; k++){
if(poolCoins[k] == _from){
i = int128(k);
}
else if(poolCoins[k] == _to){
j = int128(k);
}
// ZeroAddress signals end of list
if(poolCoins[k] == address(0) || (i != 9 && j != 9)){
break;
}
}
require(i != 9, "ExchangeIssuance: CURVE_FROM_NOT_FOUND");
require(j != 9, "ExchangeIssuance: CURVE_TO_NOT_FOUND");
return (i, j);
}
/**
* Execute exact input swap via UniswapV3
*
* @param _path List of token address to swap via.
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountIn,
uint256 _minAmountOut,
ISwapRouter02 _uniV3Router
)
private
returns (uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _amountIn);
if(_path.length == 2){
ISwapRouter02.ExactInputSingleParams memory params =
ISwapRouter02.ExactInputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactInputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, false);
ISwapRouter02.ExactInputParams memory params =
ISwapRouter02.ExactInputParams({
path: pathV3,
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut
});
uint amountOut = _uniV3Router.exactInput(params);
return amountOut;
}
}
/**
* Execute exact input swap via UniswapV2
*
* @param _path List of token address to swap via.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _router Address of uniV2 router to use
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV2(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _amountIn);
// NOTE: The following was changed from always returning result at position [1] to returning the last element of the result array
// With this change, the actual output is correctly returned also for multi-hop swaps
// See https://github.com/IndexCoop/index-coop-smart-contracts/pull/116
uint256[] memory result = _router.swapExactTokensForTokens(_amountIn, _minAmountOut, _path, address(this), block.timestamp);
// result = uint[] memory The input token amount and all subsequent output token amounts.
// we are usually only interested in the actual amount of the output token (so result element at the last place)
return result[result.length-1];
}
/**
* Gets the output amount of a token swap on Uniswap V2
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountIn
)
private
view
returns (uint256)
{
return _router.getAmountsOut(_amountIn, _swapData.path)[_swapData.path.length-1];
}
/**
* Gets the input amount of a fixed output swap on Uniswap V2.
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountOut
)
private
view
returns (uint256)
{
return _router.getAmountsIn(_amountOut, _swapData.path)[0];
}
/**
* Gets the output amount of a token swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter the uniswap v3 quoter
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountIn
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, false);
return IQuoter(_quoter).quoteExactInput(path, _amountIn);
}
/**
* Gets the input amount of a fixed output swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter uniswap v3 quoter
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountOut
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, true);
return IQuoter(_quoter).quoteExactOutput(path, _amountOut);
}
/**
* Encode path / fees to bytes in the format expected by UniV3 router
*
* @param _path List of token address to swap via (starting with input token)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _reverseOrder Boolean indicating if path needs to be reversed to start with output token.
* (which is the case for exact output swap)
*
* @return encodedPath Encoded path to be forwared to uniV3 router
*/
function _encodePathV3(
address[] memory _path,
uint24[] memory _fees,
bool _reverseOrder
)
private
pure
returns(bytes memory encodedPath)
{
if(_reverseOrder){
encodedPath = abi.encodePacked(_path[_path.length-1]);
for(uint i = 0; i < _fees.length; i++){
uint index = _fees.length - i - 1;
encodedPath = abi.encodePacked(encodedPath, _fees[index], _path[index]);
}
} else {
encodedPath = abi.encodePacked(_path[0]);
for(uint i = 0; i < _fees.length; i++){
encodedPath = abi.encodePacked(encodedPath, _fees[i], _path[i+1]);
}
}
}
function _getRouter(
Exchange _exchange,
Addresses memory _addresses
)
private
pure
returns (IUniswapV2Router02)
{
return IUniswapV2Router02(
(_exchange == Exchange.Quickswap) ? _addresses.quickRouter : _addresses.sushiRouter
);
}
}
DEXAdapterV3.sol 1147 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ICurveCalculator } from "../interfaces/external/ICurveCalculator.sol";
import { ICurveAddressProvider } from "../interfaces/external/ICurveAddressProvider.sol";
import { ICurvePoolRegistry } from "../interfaces/external/ICurvePoolRegistry.sol";
import { ICurvePool } from "../interfaces/external/ICurvePool.sol";
import { ISwapRouter02 } from "../interfaces/external/ISwapRouter02.sol";
import { IVault } from "../interfaces/external/balancer-v2/IVault.sol";
import { IQuoter } from "../interfaces/IQuoter.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title DEXAdapterV3
* @author Index Coop
*
* Same as DEXAdapterV2 but adds BalancerV2 support
*/
library DEXAdapterV3 {
using SafeERC20 for IERC20;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ Enums ============ */
enum Exchange { None, Quickswap, Sushiswap, UniV3, Curve, BalancerV2 }
/* ============ Structs ============ */
struct Addresses {
address quickRouter;
address sushiRouter;
address uniV3Router;
address uniV3Quoter;
address curveAddressProvider;
address curveCalculator;
address balV2Vault;
// Wrapped native token (WMATIC on polygon)
address weth;
}
struct SwapData {
address[] path;
uint24[] fees;
address pool; // For Curve swaps
bytes32[] poolIds; // For Balancer V2 multihop swaps
Exchange exchange;
}
struct CurvePoolData {
int128 nCoins;
uint256[8] balances;
uint256 A;
uint256 fee;
uint256[8] rates;
uint256[8] decimals;
}
/**
* Swap exact tokens for another token on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _swapData Swap data containing the path, fees, pool, and pool IDs
*
* @return amountOut The amount of output tokens
*/
function swapExactTokensForTokens(
Addresses memory _addresses,
uint256 _amountIn,
uint256 _minAmountOut,
SwapData memory _swapData
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountIn;
}
if(_swapData.exchange == Exchange.Curve){
return _swapExactTokensForTokensCurve(
_swapData.path,
_swapData.pool,
_amountIn,
_minAmountOut,
_addresses
);
}
if(_swapData.exchange== Exchange.UniV3){
return _swapExactTokensForTokensUniV3(
_swapData.path,
_swapData.fees,
_amountIn,
_minAmountOut,
ISwapRouter02(_addresses.uniV3Router)
);
}
if(_swapData.exchange == Exchange.BalancerV2){
return _swapExactTokensForTokensBalancerV2(
_swapData.path,
_amountIn,
_minAmountOut,
_swapData.poolIds,
IVault(_addresses.balV2Vault)
);
} else {
return _swapExactTokensForTokensUniV2(
_swapData.path,
_amountIn,
_minAmountOut,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Swap tokens for exact amount of output tokens on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _swapData Swap data containing the path, fees, pool, and pool IDs
*
* @return amountIn The amount of input tokens spent
*/
function swapTokensForExactTokens(
Addresses memory _addresses,
uint256 _amountOut,
uint256 _maxAmountIn,
SwapData memory _swapData
)
external
returns (uint256 amountIn)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountOut;
}
if(_swapData.exchange == Exchange.Curve){
return _swapTokensForExactTokensCurve(
_swapData.path,
_swapData.pool,
_amountOut,
_maxAmountIn,
_addresses
);
}
if(_swapData.exchange == Exchange.UniV3){
return _swapTokensForExactTokensUniV3(
_swapData.path,
_swapData.fees,
_amountOut,
_maxAmountIn,
ISwapRouter02(_addresses.uniV3Router)
);
}
if(_swapData.exchange == Exchange.BalancerV2){
return _swapTokensForExactTokensBalancerV2(
_swapData.path,
_amountOut,
_maxAmountIn,
_swapData.poolIds,
IVault(_addresses.balV2Vault)
);
} else {
return _swapTokensForExactTokensUniV2(
_swapData.path,
_amountOut,
_maxAmountIn,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Gets the output amount of a token swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function getAmountOut(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountIn
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountIn;
}
if (_swapData.exchange == Exchange.UniV3) {
return _getAmountOutUniV3(_swapData, _addresses.uniV3Quoter, _amountIn);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountOutCurve(_swapData.pool, i, j, _amountIn, _addresses);
} else if (_swapData.exchange == Exchange.BalancerV2) {
return _getAmountOutBalancerV2(
_swapData,
_addresses,
_amountIn
);
} else {
return _getAmountOutUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountIn
);
}
}
/**
* Gets the input amount of a fixed output swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function getAmountIn(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountOut
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountOut;
}
if (_swapData.exchange == Exchange.UniV3) {
return _getAmountInUniV3(_swapData, _addresses.uniV3Quoter, _amountOut);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountInCurve(_swapData.pool, i, j, _amountOut, _addresses);
} else if (_swapData.exchange == Exchange.BalancerV2) {
return _getAmountInBalancerV2(
_swapData,
_addresses,
_amountOut
);
} else {
return _getAmountInUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountOut
);
}
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/* ============ Private Methods ============ */
/**
* Execute exact output swap via a UniV2 based DEX. (such as sushiswap);
*
* @param _path List of token address to swap via.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _router Address of the uniV2 router to use
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV2(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _maxAmountIn);
return _router.swapTokensForExactTokens(_amountOut, _maxAmountIn, _path, address(this), block.timestamp)[0];
}
/**
* Execute exact output swap via UniswapV3
*
* @param _path List of token address to swap via. (In the order as
* expected by uniV2, the first element being the input toen)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountOut,
uint256 _maxAmountIn,
ISwapRouter02 _uniV3Router
)
private
returns(uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _maxAmountIn);
if(_path.length == 2){
ISwapRouter02.ExactOutputSingleParams memory params =
ISwapRouter02.ExactOutputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactOutputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, true);
ISwapRouter02.ExactOutputParams memory params =
ISwapRouter02.ExactOutputParams({
path: pathV3,
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn
});
return _uniV3Router.exactOutput(params);
}
}
/**
* Execute exact input swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
amountOut = _exchangeCurve(i, j, _pool, _amountIn, _minAmountOut, _path[0]);
}
/**
* Execute exact output swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
*
* @return amountOut The amount of output token obtained
*/
function _swapTokensForExactTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountOut,
uint256 _maxAmountIn,
Addresses memory _addresses
)
private
returns (uint256)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
uint256 returnedAmountOut = _exchangeCurve(i, j, _pool, _maxAmountIn, _amountOut, _path[0]);
require(_amountOut <= returnedAmountOut, "ExchangeIssuance: CURVE_UNDERBOUGHT");
uint256 swappedBackAmountIn;
if(returnedAmountOut > _amountOut){
swappedBackAmountIn = _exchangeCurve(j, i, _pool, returnedAmountOut.sub(_amountOut), 0, _path[1]);
}
return _maxAmountIn.sub(swappedBackAmountIn);
}
function _exchangeCurve(
int128 _i,
int128 _j,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
address _from
)
private
returns (uint256 amountOut)
{
ICurvePool pool = ICurvePool(_pool);
if(_from == ETH_ADDRESS){
amountOut = pool.exchange{value: _amountIn}(
_i,
_j,
_amountIn,
_minAmountOut
);
}
else {
IERC20(_from).approve(_pool, _amountIn);
amountOut = pool.exchange(
_i,
_j,
_amountIn,
_minAmountOut
);
}
}
/**
* Calculate required input amount to get a given output amount via Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountInCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountOut,
Addresses memory _addresses
)
private
view
returns (uint256)
{
CurvePoolData memory poolData = _getCurvePoolData(_pool, ICurveAddressProvider(_addresses.curveAddressProvider));
return ICurveCalculator(_addresses.curveCalculator).get_dx(
poolData.nCoins,
poolData.balances,
poolData.A,
poolData.fee,
poolData.rates,
poolData.decimals,
false,
_i,
_j,
_amountOut
) + ROUNDING_ERROR_MARGIN;
}
/**
* Calculate output amount of a Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountIn The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountOutCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountIn,
Addresses memory _addresses
)
private
view
returns (uint256)
{
return ICurvePool(_pool).get_dy(_i, _j, _amountIn);
}
/**
* Get metadata on curve pool required to calculate input amount from output amount
*
* @param _pool Address of curve pool to use
* @param _curveAddressProvider Address of curve address provider
*
* @return Struct containing all required data to perform getAmountInCurve calculation
*/
function _getCurvePoolData(
address _pool,
ICurveAddressProvider _curveAddressProvider
) private view returns(CurvePoolData memory)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
return CurvePoolData(
int128(registry.get_n_coins(_pool)[0]),
registry.get_balances(_pool),
registry.get_A(_pool),
registry.get_fees(_pool)[0],
registry.get_rates(_pool),
registry.get_decimals(_pool)
);
}
/**
* Get token indices for given pool
* NOTE: This was necessary sine the get_coin_indices function of the CurvePoolRegistry did not work for StEth/ETH pool
*
* @param _pool Address of curve pool to use
* @param _from Address of input token
* @param _to Address of output token
* @param _curveAddressProvider Address of curve address provider
*
* @return i Index of input token
* @return j Index of output token
*/
function _getCoinIndices(
address _pool,
address _from,
address _to,
ICurveAddressProvider _curveAddressProvider
)
private
view
returns (int128 i, int128 j)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
// Set to out of range index to signal the coin is not found yet
i = 9;
j = 9;
address[8] memory poolCoins = registry.get_coins(_pool);
for(uint256 k = 0; k < 8; k++){
if(poolCoins[k] == _from){
i = int128(k);
}
else if(poolCoins[k] == _to){
j = int128(k);
}
// ZeroAddress signals end of list
if(poolCoins[k] == address(0) || (i != 9 && j != 9)){
break;
}
}
require(i != 9, "ExchangeIssuance: CURVE_FROM_NOT_FOUND");
require(j != 9, "ExchangeIssuance: CURVE_TO_NOT_FOUND");
return (i, j);
}
/**
* Execute exact input swap via UniswapV3
*
* @param _path List of token address to swap via.
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountIn,
uint256 _minAmountOut,
ISwapRouter02 _uniV3Router
)
private
returns (uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _amountIn);
if(_path.length == 2){
ISwapRouter02.ExactInputSingleParams memory params =
ISwapRouter02.ExactInputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactInputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, false);
ISwapRouter02.ExactInputParams memory params =
ISwapRouter02.ExactInputParams({
path: pathV3,
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut
});
uint amountOut = _uniV3Router.exactInput(params);
return amountOut;
}
}
/**
* Execute exact input swap via UniswapV2
*
* @param _path List of token address to swap via.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _router Address of uniV2 router to use
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV2(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _amountIn);
// NOTE: The following was changed from always returning result at position [1] to returning the last element of the result array
// With this change, the actual output is correctly returned also for multi-hop swaps
// See https://github.com/IndexCoop/index-coop-smart-contracts/pull/116
uint256[] memory result = _router.swapExactTokensForTokens(_amountIn, _minAmountOut, _path, address(this), block.timestamp);
// result = uint[] memory The input token amount and all subsequent output token amounts.
// we are usually only interested in the actual amount of the output token (so result element at the last place)
return result[result.length-1];
}
/**
* Gets the output amount of a token swap on Uniswap V2
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountIn
)
private
view
returns (uint256)
{
return _router.getAmountsOut(_amountIn, _swapData.path)[_swapData.path.length-1];
}
/**
* Gets the input amount of a fixed output swap on Uniswap V2.
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountOut
)
private
view
returns (uint256)
{
return _router.getAmountsIn(_amountOut, _swapData.path)[0];
}
/**
* Gets the output amount of a token swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter the uniswap v3 quoter
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountIn
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, false);
return IQuoter(_quoter).quoteExactInput(path, _amountIn);
}
/**
* Gets the input amount of a fixed output swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter uniswap v3 quoter
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountOut
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, true);
return IQuoter(_quoter).quoteExactOutput(path, _amountOut);
}
/**
* Encode path / fees to bytes in the format expected by UniV3 router
*
* @param _path List of token address to swap via (starting with input token)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _reverseOrder Boolean indicating if path needs to be reversed to start with output token.
* (which is the case for exact output swap)
*
* @return encodedPath Encoded path to be forwared to uniV3 router
*/
function _encodePathV3(
address[] memory _path,
uint24[] memory _fees,
bool _reverseOrder
)
private
pure
returns(bytes memory encodedPath)
{
if(_reverseOrder){
encodedPath = abi.encodePacked(_path[_path.length-1]);
for(uint i = 0; i < _fees.length; i++){
uint index = _fees.length - i - 1;
encodedPath = abi.encodePacked(encodedPath, _fees[index], _path[index]);
}
} else {
encodedPath = abi.encodePacked(_path[0]);
for(uint i = 0; i < _fees.length; i++){
encodedPath = abi.encodePacked(encodedPath, _fees[i], _path[i+1]);
}
}
}
function _getRouter(
Exchange _exchange,
Addresses memory _addresses
)
private
pure
returns (IUniswapV2Router02)
{
return IUniswapV2Router02(
(_exchange == Exchange.Quickswap) ? _addresses.quickRouter : _addresses.sushiRouter
);
}
/**
* Execute exact input swap via Balancer V2 (supports multihop swaps)
*
* @param _path List of token addresses to swap via.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _poolIds List of pool IDs for each swap step
* @param _vault Address of the Balancer V2 Vault
*
* @return amountOut The amount of output tokens received
*/
function _swapExactTokensForTokensBalancerV2(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
bytes32[] memory _poolIds,
IVault _vault
)
private
returns (uint256 amountOut)
{
require(_path.length >= 2, "DEXAdapterV3: BALANCER_PATH_LENGTH");
require(_poolIds.length == _path.length - 1, "DEXAdapterV3: INVALID_POOL_IDS");
// Approve the Vault to spend the input token
_safeApprove(IERC20(_path[0]), address(_vault), _amountIn);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_path.length - 1);
for (uint256 i = 0; i < _path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _poolIds[i],
assetInIndex: _getAssetIndex(assets, _path[i]),
assetOutIndex: _getAssetIndex(assets, _path[i + 1]),
amount: i == 0 ? _amountIn : 0, // Only specify amount for first swap
userData: ""
});
}
// Set up funds
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Set up limits
int256[] memory limits = new int256[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == _path[0]) {
limits[i] = int256(_amountIn);
} else if (assets[i] == _path[_path.length - 1]) {
limits[i] = -int256(_minAmountOut);
} else {
limits[i] = 0;
}
}
// Perform the batch swap
int256[] memory deltas = _vault.batchSwap(
IVault.SwapKind.GIVEN_IN,
swaps,
assets,
funds,
limits,
block.timestamp
);
amountOut = uint256(-deltas[_getAssetIndex(assets, _path[_path.length - 1])]);
require(amountOut >= _minAmountOut, "DEXAdapterV3: INSUFFICIENT_OUTPUT_AMOUNT");
}
/**
* Execute exact output swap via Balancer V2 (supports multihop swaps)
*
* @param _path List of token addresses to swap via.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _poolIds List of pool IDs for each swap step
* @param _vault Address of the Balancer V2 Vault
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensBalancerV2(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
bytes32[] memory _poolIds,
IVault _vault
)
private
returns (uint256 amountIn)
{
require(_path.length >= 2, "DEXAdapterV3: BALANCER_PATH_LENGTH");
require(_poolIds.length == _path.length - 1, "DEXAdapterV3: INVALID_POOL_IDS");
// Approve the Vault to spend the input token
_safeApprove(IERC20(_path[0]), address(_vault), _maxAmountIn);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_path.length - 1);
for (uint256 i = 0; i < _path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _poolIds[i],
assetInIndex: _getAssetIndex(assets, _path[i]),
assetOutIndex: _getAssetIndex(assets, _path[i + 1]),
amount: 0, // Amount is determined by the Vault
userData: ""
});
}
// Set up funds
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Set up limits
int256[] memory limits = new int256[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == _path[0]) {
limits[i] = int256(_maxAmountIn);
} else if (assets[i] == _path[_path.length - 1]) {
limits[i] = -int256(_amountOut);
} else {
limits[i] = 0;
}
}
// Perform the batch swap
int256[] memory deltas = _vault.batchSwap(
IVault.SwapKind.GIVEN_OUT,
swaps,
assets,
funds,
limits,
block.timestamp
);
amountIn = uint256(deltas[_getAssetIndex(assets, _path[0])]);
require(amountIn <= _maxAmountIn, "DEXAdapterV3: EXCESSIVE_INPUT_AMOUNT");
}
/**
* Gets the output amount of a token swap on Balancer V2 using queryBatchSwap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses
* @param _amountIn the input amount of the trade
*
* @return amountOut the output amount of the swap
*/
function _getAmountOutBalancerV2(
SwapData memory _swapData,
Addresses memory _addresses,
uint256 _amountIn
)
private
returns (uint256 amountOut)
{
IVault _vault = IVault(_addresses.balV2Vault);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_swapData.path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_swapData.path.length - 1);
for (uint256 i = 0; i < _swapData.path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _swapData.poolIds[i],
assetInIndex: _getAssetIndex(assets, _swapData.path[i]),
assetOutIndex: _getAssetIndex(assets, _swapData.path[i + 1]),
amount: i == 0 ? _amountIn : 0, // Only specify amount for first swap
userData: ""
});
}
// Set up funds (not used in query)
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Perform the query
int256[] memory deltas = _vault.queryBatchSwap(
IVault.SwapKind.GIVEN_IN,
swaps,
assets,
funds
);
amountOut = uint256(-deltas[_getAssetIndex(assets, _swapData.path[_swapData.path.length - 1])]);
}
/**
* Gets the input amount of a fixed output swap on Balancer V2 using queryBatchSwap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses
* @param _amountOut the output amount of the swap
*
* @return amountIn the input amount of the swap
*/
function _getAmountInBalancerV2(
SwapData memory _swapData,
Addresses memory _addresses,
uint256 _amountOut
)
private
returns (uint256 amountIn)
{
IVault _vault = IVault(_addresses.balV2Vault);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_swapData.path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_swapData.path.length - 1);
for (uint256 i = 0; i < _swapData.path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _swapData.poolIds[i],
assetInIndex: _getAssetIndex(assets, _swapData.path[i]),
assetOutIndex: _getAssetIndex(assets, _swapData.path[i + 1]),
amount: i == swaps.length - 1 ? _amountOut : 0, // Only specify amount for last swap
userData: ""
});
}
// Set up funds (not used in query)
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Perform the query
int256[] memory deltas = _vault.queryBatchSwap(
IVault.SwapKind.GIVEN_OUT,
swaps,
assets,
funds
);
amountIn = uint256(deltas[_getAssetIndex(assets, _swapData.path[0])]);
}
/**
* Helper function to get the list of unique assets from the path.
*
* @param _path List of token addresses in the swap path
*
* @return assets List of unique assets
*/
function _getAssets(address[] memory _path) private pure returns (address[] memory assets) {
uint256 assetCount = 0;
address[] memory tempAssets = new address[](_path.length);
for (uint256 i = 0; i < _path.length; i++) {
bool alreadyAdded = false;
for (uint256 j = 0; j < assetCount; j++) {
if (tempAssets[j] == _path[i]) {
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
tempAssets[assetCount] = _path[i];
assetCount++;
}
}
assets = new address[](assetCount);
for (uint256 i = 0; i < assetCount; i++) {
assets[i] = tempAssets[i];
}
}
/**
* Helper function to get the index of an asset in the assets array.
*
* @param assets List of assets
* @param token Token address to find
*
* @return index Index of the token in the assets array
*/
function _getAssetIndex(address[] memory assets, address token) private pure returns (uint256) {
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == token) {
return i;
}
}
revert("DEXAdapterV3: TOKEN_NOT_IN_ASSETS");
}
}
DEXAdapterV4.sol 1305 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IAerodromeRouter } from "../interfaces/IAerodromeRouter.sol";
import { ICurveCalculator } from "../interfaces/external/ICurveCalculator.sol";
import { ICurveAddressProvider } from "../interfaces/external/ICurveAddressProvider.sol";
import { ICurvePoolRegistry } from "../interfaces/external/ICurvePoolRegistry.sol";
import { ICurvePool } from "../interfaces/external/ICurvePool.sol";
import { ISwapRouter02 } from "../interfaces/external/ISwapRouter02.sol";
import { IVault } from "../interfaces/external/balancer-v2/IVault.sol";
import { IQuoter } from "../interfaces/IQuoter.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title DEXAdapterV4
* @author Index Coop
*
* Same as DEXAdapterV3 but adds Aerodrome support
*/
library DEXAdapterV4 {
using SafeERC20 for IERC20;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ Enums ============ */
enum Exchange { None, Quickswap, Sushiswap, UniV3, Curve, BalancerV2, Aerodrome }
/* ============ Structs ============ */
struct Addresses {
address quickRouter;
address sushiRouter;
address uniV3Router;
address uniV3Quoter;
address curveAddressProvider;
address curveCalculator;
address balV2Vault;
address aerodromeRouter;
address aerodromeFactory;
// Wrapped native token (WMATIC on polygon)
address weth;
}
struct SwapData {
address[] path;
uint24[] fees;
address pool; // For Curve swaps
bytes32[] poolIds; // For Balancer V2 multihop swaps
Exchange exchange;
}
struct CurvePoolData {
int128 nCoins;
uint256[8] balances;
uint256 A;
uint256 fee;
uint256[8] rates;
uint256[8] decimals;
}
/**
* Swap exact tokens for another token on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _swapData Swap data containing the path, fees, pool, and pool IDs
*
* @return amountOut The amount of output tokens
*/
function swapExactTokensForTokens(
Addresses memory _addresses,
uint256 _amountIn,
uint256 _minAmountOut,
SwapData memory _swapData
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountIn;
}
if(_swapData.exchange == Exchange.Aerodrome){
return _swapExactTokensForTokensAerodrome(
_swapData.path,
_amountIn,
_minAmountOut,
_addresses
);
}
if(_swapData.exchange == Exchange.Curve){
return _swapExactTokensForTokensCurve(
_swapData.path,
_swapData.pool,
_amountIn,
_minAmountOut,
_addresses
);
}
if(_swapData.exchange== Exchange.UniV3){
return _swapExactTokensForTokensUniV3(
_swapData.path,
_swapData.fees,
_amountIn,
_minAmountOut,
ISwapRouter02(_addresses.uniV3Router)
);
}
if(_swapData.exchange == Exchange.BalancerV2){
return _swapExactTokensForTokensBalancerV2(
_swapData.path,
_amountIn,
_minAmountOut,
_swapData.poolIds,
IVault(_addresses.balV2Vault)
);
} else {
return _swapExactTokensForTokensUniV2(
_swapData.path,
_amountIn,
_minAmountOut,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Swap tokens for exact amount of output tokens on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _swapData Swap data containing the path, fees, pool, and pool IDs
*
* @return amountIn The amount of input tokens spent
*/
function swapTokensForExactTokens(
Addresses memory _addresses,
uint256 _amountOut,
uint256 _maxAmountIn,
SwapData memory _swapData
)
external
returns (uint256 amountIn)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountOut;
}
if(_swapData.exchange == Exchange.Aerodrome){
return _swapTokensForExactTokensAerodrome(
_swapData.path,
_amountOut,
_maxAmountIn,
_addresses
);
}
if(_swapData.exchange == Exchange.Curve){
return _swapTokensForExactTokensCurve(
_swapData.path,
_swapData.pool,
_amountOut,
_maxAmountIn,
_addresses
);
}
if(_swapData.exchange == Exchange.UniV3){
return _swapTokensForExactTokensUniV3(
_swapData.path,
_swapData.fees,
_amountOut,
_maxAmountIn,
ISwapRouter02(_addresses.uniV3Router)
);
}
if(_swapData.exchange == Exchange.BalancerV2){
return _swapTokensForExactTokensBalancerV2(
_swapData.path,
_amountOut,
_maxAmountIn,
_swapData.poolIds,
IVault(_addresses.balV2Vault)
);
} else {
return _swapTokensForExactTokensUniV2(
_swapData.path,
_amountOut,
_maxAmountIn,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Gets the output amount of a token swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function getAmountOut(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountIn
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountIn;
}
if (_swapData.exchange == Exchange.Aerodrome) {
return _getAmountOutAerodrome(_swapData, _addresses, _amountIn);
}
else if (_swapData.exchange == Exchange.UniV3) {
return _getAmountOutUniV3(_swapData, _addresses.uniV3Quoter, _amountIn);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountOutCurve(_swapData.pool, i, j, _amountIn, _addresses);
} else if (_swapData.exchange == Exchange.BalancerV2) {
return _getAmountOutBalancerV2(
_swapData,
_addresses,
_amountIn
);
} else {
return _getAmountOutUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountIn
);
}
}
/**
* Gets the input amount of a fixed output swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function getAmountIn(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountOut,
uint256 _maxAmountIn
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountOut;
}
if (_swapData.exchange == Exchange.Aerodrome) {
return _getAmountInAerodrome(_swapData, _addresses, _amountOut, _maxAmountIn);
} else if (_swapData.exchange == Exchange.UniV3) {
return _getAmountInUniV3(_swapData, _addresses.uniV3Quoter, _amountOut);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountInCurve(_swapData.pool, i, j, _amountOut, _addresses);
} else if (_swapData.exchange == Exchange.BalancerV2) {
return _getAmountInBalancerV2(
_swapData,
_addresses,
_amountOut
);
} else {
return _getAmountInUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountOut
);
}
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/* ============ Private Methods ============ */
/**
* Execute exact output swap via a UniV2 based DEX. (such as sushiswap);
*
* @param _path List of token address to swap via.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _router Address of the uniV2 router to use
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV2(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _maxAmountIn);
return _router.swapTokensForExactTokens(_amountOut, _maxAmountIn, _path, address(this), block.timestamp)[0];
}
/**
* Execute exact output swap via UniswapV3
*
* @param _path List of token address to swap via. (In the order as
* expected by uniV2, the first element being the input toen)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountOut,
uint256 _maxAmountIn,
ISwapRouter02 _uniV3Router
)
private
returns(uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _maxAmountIn);
if(_path.length == 2){
ISwapRouter02.ExactOutputSingleParams memory params =
ISwapRouter02.ExactOutputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactOutputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, true);
ISwapRouter02.ExactOutputParams memory params =
ISwapRouter02.ExactOutputParams({
path: pathV3,
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn
});
return _uniV3Router.exactOutput(params);
}
}
/**
* Execute exact input swap via Aerodrome
*
* @param _path Path (has to be of length 2)
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensAerodrome(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
require(_path.length == 2, "ExchangeIssuance: AERODROME_WRONG_PATH_LENGTH");
amountOut = _exchangeAerodrome(_amountIn, _minAmountOut, _path[0], _path[1], _addresses);
}
/**
* Execute exact input swap via Aerodrome
*
* @param _path Path (has to be of length 2)
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensAerodrome(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
Addresses memory _addresses
)
private
returns (uint256 amountIn)
{
require(_path.length == 2, "ExchangeIssuance: AERODROME_WRONG_PATH_LENGTH");
uint256 firstAmountOut = _exchangeAerodrome(_maxAmountIn, _amountOut, _path[0], _path[1], _addresses);
// Swap the excess amount received back into input token
uint256 amountSwappedBack = _exchangeAerodrome(firstAmountOut - _amountOut, 0, _path[1], _path[0], _addresses);
amountIn = _maxAmountIn - amountSwappedBack;
}
/**
* Execute exact input swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
amountOut = _exchangeCurve(i, j, _pool, _amountIn, _minAmountOut, _path[0]);
}
/**
* Execute exact output swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountOut,
uint256 _maxAmountIn,
Addresses memory _addresses
)
private
returns (uint256)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
uint256 returnedAmountOut = _exchangeCurve(i, j, _pool, _maxAmountIn, _amountOut, _path[0]);
require(_amountOut <= returnedAmountOut, "ExchangeIssuance: CURVE_UNDERBOUGHT");
uint256 swappedBackAmountIn;
if(returnedAmountOut > _amountOut){
swappedBackAmountIn = _exchangeCurve(j, i, _pool, returnedAmountOut.sub(_amountOut), 0, _path[1]);
}
return _maxAmountIn.sub(swappedBackAmountIn);
}
function _quoteAerodrome(
uint256 _amountIn,
address _from,
address _to,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
IAerodromeRouter.Route[] memory routes = new IAerodromeRouter.Route[](1);
routes[0] = IAerodromeRouter.Route(_from, _to, false, _addresses.aerodromeFactory);
_safeApprove(IERC20(_from), _addresses.aerodromeRouter, _amountIn);
return IAerodromeRouter(_addresses.aerodromeRouter).getAmountsOut(_amountIn, routes)[1];
}
function _exchangeAerodrome(
uint256 _amountIn,
uint256 _minAmountOut,
address _from,
address _to,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
IAerodromeRouter.Route[] memory routes = new IAerodromeRouter.Route[](1);
routes[0] = IAerodromeRouter.Route(_from, _to, false, _addresses.aerodromeFactory);
_safeApprove(IERC20(_from), _addresses.aerodromeRouter, _amountIn);
return IAerodromeRouter(_addresses.aerodromeRouter).swapExactTokensForTokens(
_amountIn,
_minAmountOut,
routes,
address(this),
block.timestamp
)[1];
}
function _exchangeCurve(
int128 _i,
int128 _j,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
address _from
)
private
returns (uint256 amountOut)
{
ICurvePool pool = ICurvePool(_pool);
if(_from == ETH_ADDRESS){
amountOut = pool.exchange{value: _amountIn}(
_i,
_j,
_amountIn,
_minAmountOut
);
}
else {
IERC20(_from).approve(_pool, _amountIn);
amountOut = pool.exchange(
_i,
_j,
_amountIn,
_minAmountOut
);
}
}
/**
* Calculate required input amount to get a given output amount via Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountInCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountOut,
Addresses memory _addresses
)
private
view
returns (uint256)
{
CurvePoolData memory poolData = _getCurvePoolData(_pool, ICurveAddressProvider(_addresses.curveAddressProvider));
return ICurveCalculator(_addresses.curveCalculator).get_dx(
poolData.nCoins,
poolData.balances,
poolData.A,
poolData.fee,
poolData.rates,
poolData.decimals,
false,
_i,
_j,
_amountOut
) + ROUNDING_ERROR_MARGIN;
}
/**
* Calculate output amount of a Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountIn The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountOutCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountIn,
Addresses memory _addresses
)
private
view
returns (uint256)
{
return ICurvePool(_pool).get_dy(_i, _j, _amountIn);
}
/**
* Get metadata on curve pool required to calculate input amount from output amount
*
* @param _pool Address of curve pool to use
* @param _curveAddressProvider Address of curve address provider
*
* @return Struct containing all required data to perform getAmountInCurve calculation
*/
function _getCurvePoolData(
address _pool,
ICurveAddressProvider _curveAddressProvider
) private view returns(CurvePoolData memory)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
return CurvePoolData(
int128(registry.get_n_coins(_pool)[0]),
registry.get_balances(_pool),
registry.get_A(_pool),
registry.get_fees(_pool)[0],
registry.get_rates(_pool),
registry.get_decimals(_pool)
);
}
/**
* Get token indices for given pool
* NOTE: This was necessary sine the get_coin_indices function of the CurvePoolRegistry did not work for StEth/ETH pool
*
* @param _pool Address of curve pool to use
* @param _from Address of input token
* @param _to Address of output token
* @param _curveAddressProvider Address of curve address provider
*
* @return i Index of input token
* @return j Index of output token
*/
function _getCoinIndices(
address _pool,
address _from,
address _to,
ICurveAddressProvider _curveAddressProvider
)
private
view
returns (int128 i, int128 j)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
// Set to out of range index to signal the coin is not found yet
i = 9;
j = 9;
address[8] memory poolCoins = registry.get_coins(_pool);
for(uint256 k = 0; k < 8; k++){
if(poolCoins[k] == _from){
i = int128(k);
}
else if(poolCoins[k] == _to){
j = int128(k);
}
// ZeroAddress signals end of list
if(poolCoins[k] == address(0) || (i != 9 && j != 9)){
break;
}
}
require(i != 9, "ExchangeIssuance: CURVE_FROM_NOT_FOUND");
require(j != 9, "ExchangeIssuance: CURVE_TO_NOT_FOUND");
return (i, j);
}
/**
* Execute exact input swap via UniswapV3
*
* @param _path List of token address to swap via.
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountIn,
uint256 _minAmountOut,
ISwapRouter02 _uniV3Router
)
private
returns (uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _amountIn);
if(_path.length == 2){
ISwapRouter02.ExactInputSingleParams memory params =
ISwapRouter02.ExactInputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactInputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, false);
ISwapRouter02.ExactInputParams memory params =
ISwapRouter02.ExactInputParams({
path: pathV3,
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut
});
uint amountOut = _uniV3Router.exactInput(params);
return amountOut;
}
}
/**
* Execute exact input swap via UniswapV2
*
* @param _path List of token address to swap via.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _router Address of uniV2 router to use
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV2(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _amountIn);
// NOTE: The following was changed from always returning result at position [1] to returning the last element of the result array
// With this change, the actual output is correctly returned also for multi-hop swaps
// See https://github.com/IndexCoop/index-coop-smart-contracts/pull/116
uint256[] memory result = _router.swapExactTokensForTokens(_amountIn, _minAmountOut, _path, address(this), block.timestamp);
// result = uint[] memory The input token amount and all subsequent output token amounts.
// we are usually only interested in the actual amount of the output token (so result element at the last place)
return result[result.length-1];
}
/**
* Gets the output amount of a token swap on Uniswap V2
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountIn
)
private
view
returns (uint256)
{
return _router.getAmountsOut(_amountIn, _swapData.path)[_swapData.path.length-1];
}
/**
* Gets the input amount of a fixed output swap on Uniswap V2.
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountOut
)
private
view
returns (uint256)
{
return _router.getAmountsIn(_amountOut, _swapData.path)[0];
}
/**
* Gets the output amount of a token swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutAerodrome(
SwapData memory _swapData,
Addresses memory _addresses,
uint256 _amountIn
)
private
returns (uint256)
{
require(_swapData.path.length == 2, "ExchangeIssuance: AERODROME_WRONG_PATH_LENGTH");
return _quoteAerodrome(_amountIn, _swapData.path[0], _swapData.path[1], _addresses);
}
/**
* Gets the input amount of a fixed output swap on Aerodrome
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut the output amount of the swap
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInAerodrome(
SwapData memory _swapData,
Addresses memory _addresses,
uint256 _amountOut,
uint256 _maxAmountIn
)
private
returns (uint256)
{
require(_swapData.path.length == 2, "ExchangeIssuance: AERODROME_WRONG_PATH_LENGTH");
uint256 firstAmountOut = _quoteAerodrome(_maxAmountIn, _swapData.path[0], _swapData.path[1], _addresses);
// Note: Probably inaccurate since the state of the underlying liquidity would change from the first swap, which is not accounted for here
uint256 amountSwappedBack = _quoteAerodrome(firstAmountOut - _amountOut, _swapData.path[1], _swapData.path[0], _addresses);
return _maxAmountIn - amountSwappedBack;
}
/**
* Gets the output amount of a token swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter the uniswap v3 quoter
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountIn
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, false);
return IQuoter(_quoter).quoteExactInput(path, _amountIn);
}
/**
* Gets the input amount of a fixed output swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter uniswap v3 quoter
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountOut
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, true);
return IQuoter(_quoter).quoteExactOutput(path, _amountOut);
}
/**
* Encode path / fees to bytes in the format expected by UniV3 router
*
* @param _path List of token address to swap via (starting with input token)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _reverseOrder Boolean indicating if path needs to be reversed to start with output token.
* (which is the case for exact output swap)
*
* @return encodedPath Encoded path to be forwared to uniV3 router
*/
function _encodePathV3(
address[] memory _path,
uint24[] memory _fees,
bool _reverseOrder
)
private
pure
returns(bytes memory encodedPath)
{
if(_reverseOrder){
encodedPath = abi.encodePacked(_path[_path.length-1]);
for(uint i = 0; i < _fees.length; i++){
uint index = _fees.length - i - 1;
encodedPath = abi.encodePacked(encodedPath, _fees[index], _path[index]);
}
} else {
encodedPath = abi.encodePacked(_path[0]);
for(uint i = 0; i < _fees.length; i++){
encodedPath = abi.encodePacked(encodedPath, _fees[i], _path[i+1]);
}
}
}
function _getRouter(
Exchange _exchange,
Addresses memory _addresses
)
private
pure
returns (IUniswapV2Router02)
{
return IUniswapV2Router02(
(_exchange == Exchange.Quickswap) ? _addresses.quickRouter : _addresses.sushiRouter
);
}
/**
* Execute exact input swap via Balancer V2 (supports multihop swaps)
*
* @param _path List of token addresses to swap via.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _poolIds List of pool IDs for each swap step
* @param _vault Address of the Balancer V2 Vault
*
* @return amountOut The amount of output tokens received
*/
function _swapExactTokensForTokensBalancerV2(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
bytes32[] memory _poolIds,
IVault _vault
)
private
returns (uint256 amountOut)
{
require(_path.length >= 2, "DEXAdapterV3: BALANCER_PATH_LENGTH");
require(_poolIds.length == _path.length - 1, "DEXAdapterV3: INVALID_POOL_IDS");
// Approve the Vault to spend the input token
_safeApprove(IERC20(_path[0]), address(_vault), _amountIn);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_path.length - 1);
for (uint256 i = 0; i < _path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _poolIds[i],
assetInIndex: _getAssetIndex(assets, _path[i]),
assetOutIndex: _getAssetIndex(assets, _path[i + 1]),
amount: i == 0 ? _amountIn : 0, // Only specify amount for first swap
userData: ""
});
}
// Set up funds
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Set up limits
int256[] memory limits = new int256[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == _path[0]) {
limits[i] = int256(_amountIn);
} else if (assets[i] == _path[_path.length - 1]) {
limits[i] = -int256(_minAmountOut);
} else {
limits[i] = 0;
}
}
// Perform the batch swap
int256[] memory deltas = _vault.batchSwap(
IVault.SwapKind.GIVEN_IN,
swaps,
assets,
funds,
limits,
block.timestamp
);
amountOut = uint256(-deltas[_getAssetIndex(assets, _path[_path.length - 1])]);
require(amountOut >= _minAmountOut, "DEXAdapterV3: INSUFFICIENT_OUTPUT_AMOUNT");
}
/**
* Execute exact output swap via Balancer V2 (supports multihop swaps)
*
* @param _path List of token addresses to swap via.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _poolIds List of pool IDs for each swap step
* @param _vault Address of the Balancer V2 Vault
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensBalancerV2(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
bytes32[] memory _poolIds,
IVault _vault
)
private
returns (uint256 amountIn)
{
require(_path.length >= 2, "DEXAdapterV3: BALANCER_PATH_LENGTH");
require(_poolIds.length == _path.length - 1, "DEXAdapterV3: INVALID_POOL_IDS");
// Approve the Vault to spend the input token
_safeApprove(IERC20(_path[0]), address(_vault), _maxAmountIn);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_path.length - 1);
for (uint256 i = 0; i < _path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _poolIds[i],
assetInIndex: _getAssetIndex(assets, _path[i]),
assetOutIndex: _getAssetIndex(assets, _path[i + 1]),
amount: 0, // Amount is determined by the Vault
userData: ""
});
}
// Set up funds
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Set up limits
int256[] memory limits = new int256[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == _path[0]) {
limits[i] = int256(_maxAmountIn);
} else if (assets[i] == _path[_path.length - 1]) {
limits[i] = -int256(_amountOut);
} else {
limits[i] = 0;
}
}
// Perform the batch swap
int256[] memory deltas = _vault.batchSwap(
IVault.SwapKind.GIVEN_OUT,
swaps,
assets,
funds,
limits,
block.timestamp
);
amountIn = uint256(deltas[_getAssetIndex(assets, _path[0])]);
require(amountIn <= _maxAmountIn, "DEXAdapterV3: EXCESSIVE_INPUT_AMOUNT");
}
/**
* Gets the output amount of a token swap on Balancer V2 using queryBatchSwap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses
* @param _amountIn the input amount of the trade
*
* @return amountOut the output amount of the swap
*/
function _getAmountOutBalancerV2(
SwapData memory _swapData,
Addresses memory _addresses,
uint256 _amountIn
)
private
returns (uint256 amountOut)
{
IVault _vault = IVault(_addresses.balV2Vault);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_swapData.path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_swapData.path.length - 1);
for (uint256 i = 0; i < _swapData.path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _swapData.poolIds[i],
assetInIndex: _getAssetIndex(assets, _swapData.path[i]),
assetOutIndex: _getAssetIndex(assets, _swapData.path[i + 1]),
amount: i == 0 ? _amountIn : 0, // Only specify amount for first swap
userData: ""
});
}
// Set up funds (not used in query)
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Perform the query
int256[] memory deltas = _vault.queryBatchSwap(
IVault.SwapKind.GIVEN_IN,
swaps,
assets,
funds
);
amountOut = uint256(-deltas[_getAssetIndex(assets, _swapData.path[_swapData.path.length - 1])]);
}
/**
* Gets the input amount of a fixed output swap on Balancer V2 using queryBatchSwap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses
* @param _amountOut the output amount of the swap
*
* @return amountIn the input amount of the swap
*/
function _getAmountInBalancerV2(
SwapData memory _swapData,
Addresses memory _addresses,
uint256 _amountOut
)
private
returns (uint256 amountIn)
{
IVault _vault = IVault(_addresses.balV2Vault);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_swapData.path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_swapData.path.length - 1);
for (uint256 i = 0; i < _swapData.path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _swapData.poolIds[i],
assetInIndex: _getAssetIndex(assets, _swapData.path[i]),
assetOutIndex: _getAssetIndex(assets, _swapData.path[i + 1]),
amount: i == swaps.length - 1 ? _amountOut : 0, // Only specify amount for last swap
userData: ""
});
}
// Set up funds (not used in query)
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Perform the query
int256[] memory deltas = _vault.queryBatchSwap(
IVault.SwapKind.GIVEN_OUT,
swaps,
assets,
funds
);
amountIn = uint256(deltas[_getAssetIndex(assets, _swapData.path[0])]);
}
/**
* Helper function to get the list of unique assets from the path.
*
* @param _path List of token addresses in the swap path
*
* @return assets List of unique assets
*/
function _getAssets(address[] memory _path) private pure returns (address[] memory assets) {
uint256 assetCount = 0;
address[] memory tempAssets = new address[](_path.length);
for (uint256 i = 0; i < _path.length; i++) {
bool alreadyAdded = false;
for (uint256 j = 0; j < assetCount; j++) {
if (tempAssets[j] == _path[i]) {
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
tempAssets[assetCount] = _path[i];
assetCount++;
}
}
assets = new address[](assetCount);
for (uint256 i = 0; i < assetCount; i++) {
assets[i] = tempAssets[i];
}
}
/**
* Helper function to get the index of an asset in the assets array.
*
* @param assets List of assets
* @param token Token address to find
*
* @return index Index of the token in the assets array
*/
function _getAssetIndex(address[] memory assets, address token) private pure returns (uint256) {
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == token) {
return i;
}
}
revert("DEXAdapterV3: TOKEN_NOT_IN_ASSETS");
}
}
DEXAdapterV5.sol 1517 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IAerodromeRouter } from "../interfaces/IAerodromeRouter.sol";
import { ICurveCalculator } from "../interfaces/external/ICurveCalculator.sol";
import { ICurveAddressProvider } from "../interfaces/external/ICurveAddressProvider.sol";
import { ICurvePoolRegistry } from "../interfaces/external/ICurvePoolRegistry.sol";
import { ICurvePool } from "../interfaces/external/ICurvePool.sol";
import { ISwapRouter02 } from "../interfaces/external/ISwapRouter02.sol";
import { IVault } from "../interfaces/external/balancer-v2/IVault.sol";
import { IQuoter } from "../interfaces/IQuoter.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { IAerodromeSlipstreamRouter } from "../interfaces/IAerodromeSlipstreamRouter.sol";
import { IAerodromeSlipstreamQuoter } from "../interfaces/IAerodromeSlipstreamQuoter.sol";
/**
* @title DEXAdapterV5
* @author Index Coop
*
* Same as DEXAdapterV4 but adds Aerodrome Slipstream support
*/
library DEXAdapterV5 {
using SafeERC20 for IERC20;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ Enums ============ */
enum Exchange { None, Quickswap, Sushiswap, UniV3, Curve, BalancerV2, Aerodrome, AerodromeSlipstream }
/* ============ Structs ============ */
struct Addresses {
address quickRouter;
address sushiRouter;
address uniV3Router;
address uniV3Quoter;
address curveAddressProvider;
address curveCalculator;
address balV2Vault;
address aerodromeRouter;
address aerodromeFactory;
address aerodromeSlipstreamRouter;
address aerodromeSlipstreamQuoter;
// Wrapped native token (WMATIC on polygon)
address weth;
}
struct SwapData {
address[] path;
uint24[] fees;
int24[] tickSpacing;
address pool; // For Curve swaps
bytes32[] poolIds; // For Balancer V2 multihop swaps
Exchange exchange;
}
struct CurvePoolData {
int128 nCoins;
uint256[8] balances;
uint256 A;
uint256 fee;
uint256[8] rates;
uint256[8] decimals;
}
/**
* Swap exact tokens for another token on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _swapData Swap data containing the path, fees, pool, and pool IDs
*
* @return amountOut The amount of output tokens
*/
function swapExactTokensForTokens(
Addresses memory _addresses,
uint256 _amountIn,
uint256 _minAmountOut,
SwapData memory _swapData
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountIn;
}
if(_swapData.exchange == Exchange.AerodromeSlipstream){
return _swapExactTokensForTokensAerodromeSlipstream(
_swapData.path,
_swapData.tickSpacing,
_amountIn,
_minAmountOut,
IAerodromeSlipstreamRouter(_addresses.aerodromeSlipstreamRouter)
);
}
if(_swapData.exchange == Exchange.Aerodrome){
return _swapExactTokensForTokensAerodrome(
_swapData.path,
_amountIn,
_minAmountOut,
_addresses
);
}
if(_swapData.exchange == Exchange.Curve){
return _swapExactTokensForTokensCurve(
_swapData.path,
_swapData.pool,
_amountIn,
_minAmountOut,
_addresses
);
}
if(_swapData.exchange== Exchange.UniV3){
return _swapExactTokensForTokensUniV3(
_swapData.path,
_swapData.fees,
_amountIn,
_minAmountOut,
ISwapRouter02(_addresses.uniV3Router)
);
}
if(_swapData.exchange == Exchange.BalancerV2){
return _swapExactTokensForTokensBalancerV2(
_swapData.path,
_amountIn,
_minAmountOut,
_swapData.poolIds,
IVault(_addresses.balV2Vault)
);
} else {
return _swapExactTokensForTokensUniV2(
_swapData.path,
_amountIn,
_minAmountOut,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Swap tokens for exact amount of output tokens on a given DEX.
*
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _swapData Swap data containing the path, fees, pool, and pool IDs
*
* @return amountIn The amount of input tokens spent
*/
function swapTokensForExactTokens(
Addresses memory _addresses,
uint256 _amountOut,
uint256 _maxAmountIn,
SwapData memory _swapData
)
external
returns (uint256 amountIn)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length -1]) {
return _amountOut;
}
if(_swapData.exchange == Exchange.AerodromeSlipstream){
return _swapTokensForExactTokensAerodromeSlipstream(
_swapData.path,
_swapData.tickSpacing,
_amountOut,
_maxAmountIn,
IAerodromeSlipstreamRouter(_addresses.aerodromeSlipstreamRouter)
);
}
if(_swapData.exchange == Exchange.Aerodrome){
return _swapTokensForExactTokensAerodrome(
_swapData.path,
_amountOut,
_maxAmountIn,
_addresses
);
}
if(_swapData.exchange == Exchange.Curve){
return _swapTokensForExactTokensCurve(
_swapData.path,
_swapData.pool,
_amountOut,
_maxAmountIn,
_addresses
);
}
if(_swapData.exchange == Exchange.UniV3){
return _swapTokensForExactTokensUniV3(
_swapData.path,
_swapData.fees,
_amountOut,
_maxAmountIn,
ISwapRouter02(_addresses.uniV3Router)
);
}
if(_swapData.exchange == Exchange.BalancerV2){
return _swapTokensForExactTokensBalancerV2(
_swapData.path,
_amountOut,
_maxAmountIn,
_swapData.poolIds,
IVault(_addresses.balV2Vault)
);
} else {
return _swapTokensForExactTokensUniV2(
_swapData.path,
_amountOut,
_maxAmountIn,
_getRouter(_swapData.exchange, _addresses)
);
}
}
/**
* Gets the output amount of a token swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function getAmountOut(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountIn
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountIn;
}
if (_swapData.exchange == Exchange.AerodromeSlipstream) {
return _getAmountOutAerodromeSlipstream(_swapData, _addresses.aerodromeSlipstreamQuoter, _amountIn);
}
if (_swapData.exchange == Exchange.Aerodrome) {
return _getAmountOutAerodrome(_swapData, _addresses, _amountIn);
}
else if (_swapData.exchange == Exchange.UniV3) {
return _getAmountOutUniV3(_swapData, _addresses.uniV3Quoter, _amountIn);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountOutCurve(_swapData.pool, i, j, _amountIn, _addresses);
} else if (_swapData.exchange == Exchange.BalancerV2) {
return _getAmountOutBalancerV2(
_swapData,
_addresses,
_amountIn
);
} else {
return _getAmountOutUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountIn
);
}
}
/**
* Gets the input amount of a fixed output swap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function getAmountIn(
Addresses memory _addresses,
SwapData memory _swapData,
uint256 _amountOut,
uint256 _maxAmountIn
)
external
returns (uint256)
{
if (_swapData.path.length == 0 || _swapData.path[0] == _swapData.path[_swapData.path.length-1]) {
return _amountOut;
}
if (_swapData.exchange == Exchange.AerodromeSlipstream) {
return _getAmountInAerodromeSlipstream(_swapData, _addresses.aerodromeSlipstreamQuoter, _amountOut);
}
if (_swapData.exchange == Exchange.Aerodrome) {
return _getAmountInAerodrome(_swapData, _addresses, _amountOut, _maxAmountIn);
} else if (_swapData.exchange == Exchange.UniV3) {
return _getAmountInUniV3(_swapData, _addresses.uniV3Quoter, _amountOut);
} else if (_swapData.exchange == Exchange.Curve) {
(int128 i, int128 j) = _getCoinIndices(
_swapData.pool,
_swapData.path[0],
_swapData.path[1],
ICurveAddressProvider(_addresses.curveAddressProvider)
);
return _getAmountInCurve(_swapData.pool, i, j, _amountOut, _addresses);
} else if (_swapData.exchange == Exchange.BalancerV2) {
return _getAmountInBalancerV2(
_swapData,
_addresses,
_amountOut
);
} else {
return _getAmountInUniV2(
_swapData,
_getRouter(_swapData.exchange, _addresses),
_amountOut
);
}
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/* ============ Private Methods ============ */
/**
* Execute exact output swap via a UniV2 based DEX. (such as sushiswap);
*
* @param _path List of token address to swap via.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _router Address of the uniV2 router to use
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV2(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _maxAmountIn);
return _router.swapTokensForExactTokens(_amountOut, _maxAmountIn, _path, address(this), block.timestamp)[0];
}
/**
* Execute exact output swap via Aerodrome Slipstream
*
* @param _path List of token address to swap via. (the first element being the input toen)
* @param _tickSpacing List of tick Spacing identifying the pools to swap via.
* (_tickSpacing[0] refers to pool between _path[0] and _path[1])
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _aerodromeSlipstreamRouter Address of the Aerodrome Slipstream router
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensAerodromeSlipstream(
address[] memory _path,
int24[] memory _tickSpacing,
uint256 _amountOut,
uint256 _maxAmountIn,
IAerodromeSlipstreamRouter _aerodromeSlipstreamRouter
)
private
returns(uint256)
{
require(_path.length == _tickSpacing.length + 1, "ExchangeIssuance: PATHS_tickSpacing_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_aerodromeSlipstreamRouter), _maxAmountIn);
if(_path.length == 2){
IAerodromeSlipstreamRouter.ExactOutputSingleParams memory params =
IAerodromeSlipstreamRouter.ExactOutputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
tickSpacing: _tickSpacing[0],
recipient: address(this),
deadline: block.timestamp,
amountOut: _amountOut,
amountInMaximum: _maxAmountIn,
sqrtPriceLimitX96: 0
});
return _aerodromeSlipstreamRouter.exactOutputSingle(params);
} else {
bytes memory pathV3 = _encodePathSlipstream(_path, _tickSpacing, true);
IAerodromeSlipstreamRouter.ExactOutputParams memory params =
IAerodromeSlipstreamRouter.ExactOutputParams({
path: pathV3,
recipient: address(this),
amountOut: _amountOut,
deadline: block.timestamp,
amountInMaximum: _maxAmountIn
});
return _aerodromeSlipstreamRouter.exactOutput(params);
}
}
/**
* Execute exact output swap via UniswapV3
*
* @param _path List of token address to swap via. (In the order as
* expected by uniV2, the first element being the input toen)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountOut,
uint256 _maxAmountIn,
ISwapRouter02 _uniV3Router
)
private
returns(uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _maxAmountIn);
if(_path.length == 2){
ISwapRouter02.ExactOutputSingleParams memory params =
ISwapRouter02.ExactOutputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactOutputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, true);
ISwapRouter02.ExactOutputParams memory params =
ISwapRouter02.ExactOutputParams({
path: pathV3,
recipient: address(this),
amountOut: _amountOut,
amountInMaximum: _maxAmountIn
});
return _uniV3Router.exactOutput(params);
}
}
/**
* Execute exact input swap via Aerodrome
*
* @param _path Path (has to be of length 2)
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensAerodrome(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
require(_path.length == 2, "ExchangeIssuance: AERODROME_WRONG_PATH_LENGTH");
amountOut = _exchangeAerodrome(_amountIn, _minAmountOut, _path[0], _path[1], _addresses);
}
/**
* Execute exact input swap via Aerodrome
*
* @param _path Path (has to be of length 2)
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensAerodrome(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
Addresses memory _addresses
)
private
returns (uint256 amountIn)
{
require(_path.length == 2, "ExchangeIssuance: AERODROME_WRONG_PATH_LENGTH");
uint256 firstAmountOut = _exchangeAerodrome(_maxAmountIn, _amountOut, _path[0], _path[1], _addresses);
// Swap the excess amount received back into input token
uint256 amountSwappedBack = _exchangeAerodrome(firstAmountOut - _amountOut, 0, _path[1], _path[0], _addresses);
amountIn = _maxAmountIn - amountSwappedBack;
}
/**
* Execute exact input swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
amountOut = _exchangeCurve(i, j, _pool, _amountIn, _minAmountOut, _path[0]);
}
/**
* Execute exact output swap via Curve
*
* @param _path Path (has to be of length 2)
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensCurve(
address[] memory _path,
address _pool,
uint256 _amountOut,
uint256 _maxAmountIn,
Addresses memory _addresses
)
private
returns (uint256)
{
require(_path.length == 2, "ExchangeIssuance: CURVE_WRONG_PATH_LENGTH");
(int128 i, int128 j) = _getCoinIndices(_pool, _path[0], _path[1], ICurveAddressProvider(_addresses.curveAddressProvider));
uint256 returnedAmountOut = _exchangeCurve(i, j, _pool, _maxAmountIn, _amountOut, _path[0]);
require(_amountOut <= returnedAmountOut, "ExchangeIssuance: CURVE_UNDERBOUGHT");
uint256 swappedBackAmountIn;
if(returnedAmountOut > _amountOut){
swappedBackAmountIn = _exchangeCurve(j, i, _pool, returnedAmountOut.sub(_amountOut), 0, _path[1]);
}
return _maxAmountIn.sub(swappedBackAmountIn);
}
function _quoteAerodrome(
uint256 _amountIn,
address _from,
address _to,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
IAerodromeRouter.Route[] memory routes = new IAerodromeRouter.Route[](1);
routes[0] = IAerodromeRouter.Route(_from, _to, false, _addresses.aerodromeFactory);
_safeApprove(IERC20(_from), _addresses.aerodromeRouter, _amountIn);
return IAerodromeRouter(_addresses.aerodromeRouter).getAmountsOut(_amountIn, routes)[1];
}
function _exchangeAerodrome(
uint256 _amountIn,
uint256 _minAmountOut,
address _from,
address _to,
Addresses memory _addresses
)
private
returns (uint256 amountOut)
{
IAerodromeRouter.Route[] memory routes = new IAerodromeRouter.Route[](1);
routes[0] = IAerodromeRouter.Route(_from, _to, false, _addresses.aerodromeFactory);
_safeApprove(IERC20(_from), _addresses.aerodromeRouter, _amountIn);
return IAerodromeRouter(_addresses.aerodromeRouter).swapExactTokensForTokens(
_amountIn,
_minAmountOut,
routes,
address(this),
block.timestamp
)[1];
}
function _exchangeCurve(
int128 _i,
int128 _j,
address _pool,
uint256 _amountIn,
uint256 _minAmountOut,
address _from
)
private
returns (uint256 amountOut)
{
ICurvePool pool = ICurvePool(_pool);
if(_from == ETH_ADDRESS){
amountOut = pool.exchange{value: _amountIn}(
_i,
_j,
_amountIn,
_minAmountOut
);
}
else {
IERC20(_from).approve(_pool, _amountIn);
amountOut = pool.exchange(
_i,
_j,
_amountIn,
_minAmountOut
);
}
}
/**
* Calculate required input amount to get a given output amount via Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountOut The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountInCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountOut,
Addresses memory _addresses
)
private
view
returns (uint256)
{
CurvePoolData memory poolData = _getCurvePoolData(_pool, ICurveAddressProvider(_addresses.curveAddressProvider));
return ICurveCalculator(_addresses.curveCalculator).get_dx(
poolData.nCoins,
poolData.balances,
poolData.A,
poolData.fee,
poolData.rates,
poolData.decimals,
false,
_i,
_j,
_amountOut
) + ROUNDING_ERROR_MARGIN;
}
/**
* Calculate output amount of a Curve swap
*
* @param _i Index of input token as per the ordering of the pools tokens
* @param _j Index of output token as per the ordering of the pools tokens
* @param _pool Address of curve pool to use
* @param _amountIn The amount of output token to be received
* @param _addresses Struct containing relevant smart contract addresses.
*
* @return amountOut The amount of output token obtained
*/
function _getAmountOutCurve(
address _pool,
int128 _i,
int128 _j,
uint256 _amountIn,
Addresses memory _addresses
)
private
view
returns (uint256)
{
return ICurvePool(_pool).get_dy(_i, _j, _amountIn);
}
/**
* Get metadata on curve pool required to calculate input amount from output amount
*
* @param _pool Address of curve pool to use
* @param _curveAddressProvider Address of curve address provider
*
* @return Struct containing all required data to perform getAmountInCurve calculation
*/
function _getCurvePoolData(
address _pool,
ICurveAddressProvider _curveAddressProvider
) private view returns(CurvePoolData memory)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
return CurvePoolData(
int128(registry.get_n_coins(_pool)[0]),
registry.get_balances(_pool),
registry.get_A(_pool),
registry.get_fees(_pool)[0],
registry.get_rates(_pool),
registry.get_decimals(_pool)
);
}
/**
* Get token indices for given pool
* NOTE: This was necessary sine the get_coin_indices function of the CurvePoolRegistry did not work for StEth/ETH pool
*
* @param _pool Address of curve pool to use
* @param _from Address of input token
* @param _to Address of output token
* @param _curveAddressProvider Address of curve address provider
*
* @return i Index of input token
* @return j Index of output token
*/
function _getCoinIndices(
address _pool,
address _from,
address _to,
ICurveAddressProvider _curveAddressProvider
)
private
view
returns (int128 i, int128 j)
{
ICurvePoolRegistry registry = ICurvePoolRegistry(_curveAddressProvider.get_registry());
// Set to out of range index to signal the coin is not found yet
i = 9;
j = 9;
address[8] memory poolCoins = registry.get_coins(_pool);
for(uint256 k = 0; k < 8; k++){
if(poolCoins[k] == _from){
i = int128(k);
}
else if(poolCoins[k] == _to){
j = int128(k);
}
// ZeroAddress signals end of list
if(poolCoins[k] == address(0) || (i != 9 && j != 9)){
break;
}
}
require(i != 9, "ExchangeIssuance: CURVE_FROM_NOT_FOUND");
require(j != 9, "ExchangeIssuance: CURVE_TO_NOT_FOUND");
return (i, j);
}
/**
* Execute exact input swap via Aerodrome Slipstream
*
* @param _path List of token address to swap via.
* @param _tickSpacing List of fee levels identifying the pools to swap via.
* (_tickSpacing[0] refers to pool between _path[0] and _path[1])
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _aerodromeRouter Address of the Aerodrome Slipstream router
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensAerodromeSlipstream(
address[] memory _path,
int24[] memory _tickSpacing,
uint256 _amountIn,
uint256 _minAmountOut,
IAerodromeSlipstreamRouter _aerodromeRouter
)
private
returns (uint256)
{
require(_path.length == _tickSpacing.length + 1, "ExchangeIssuance: PATHS_TICK_SPACING_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_aerodromeRouter), _amountIn);
if(_path.length == 2){
IAerodromeSlipstreamRouter.ExactInputSingleParams memory params =
IAerodromeSlipstreamRouter.ExactInputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
tickSpacing: _tickSpacing[0],
recipient: address(this),
deadline: block.timestamp,
amountIn: _amountIn,
amountOutMinimum: _minAmountOut,
sqrtPriceLimitX96: 0
});
return _aerodromeRouter.exactInputSingle(params);
} else {
bytes memory pathV3 = _encodePathSlipstream(_path, _tickSpacing, false);
IAerodromeSlipstreamRouter.ExactInputParams memory params =
IAerodromeSlipstreamRouter.ExactInputParams({
path: pathV3,
recipient: address(this),
deadline: block.timestamp,
amountIn: _amountIn,
amountOutMinimum: _minAmountOut
});
uint amountOut = _aerodromeRouter.exactInput(params);
return amountOut;
}
}
/**
* Execute exact input swap via UniswapV3
*
* @param _path List of token address to swap via.
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _uniV3Router Address of the uniswapV3 router
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV3(
address[] memory _path,
uint24[] memory _fees,
uint256 _amountIn,
uint256 _minAmountOut,
ISwapRouter02 _uniV3Router
)
private
returns (uint256)
{
require(_path.length == _fees.length + 1, "ExchangeIssuance: PATHS_FEES_MISMATCH");
_safeApprove(IERC20(_path[0]), address(_uniV3Router), _amountIn);
if(_path.length == 2){
ISwapRouter02.ExactInputSingleParams memory params =
ISwapRouter02.ExactInputSingleParams({
tokenIn: _path[0],
tokenOut: _path[1],
fee: _fees[0],
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut,
sqrtPriceLimitX96: 0
});
return _uniV3Router.exactInputSingle(params);
} else {
bytes memory pathV3 = _encodePathV3(_path, _fees, false);
ISwapRouter02.ExactInputParams memory params =
ISwapRouter02.ExactInputParams({
path: pathV3,
recipient: address(this),
amountIn: _amountIn,
amountOutMinimum: _minAmountOut
});
uint amountOut = _uniV3Router.exactInput(params);
return amountOut;
}
}
/**
* Execute exact input swap via UniswapV2
*
* @param _path List of token address to swap via.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _router Address of uniV2 router to use
*
* @return amountOut The amount of output token obtained
*/
function _swapExactTokensForTokensUniV2(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
IUniswapV2Router02 _router
)
private
returns (uint256)
{
_safeApprove(IERC20(_path[0]), address(_router), _amountIn);
// NOTE: The following was changed from always returning result at position [1] to returning the last element of the result array
// With this change, the actual output is correctly returned also for multi-hop swaps
// See https://github.com/IndexCoop/index-coop-smart-contracts/pull/116
uint256[] memory result = _router.swapExactTokensForTokens(_amountIn, _minAmountOut, _path, address(this), block.timestamp);
// result = uint[] memory The input token amount and all subsequent output token amounts.
// we are usually only interested in the actual amount of the output token (so result element at the last place)
return result[result.length-1];
}
/**
* Gets the output amount of a token swap on Uniswap V2
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountIn
)
private
view
returns (uint256)
{
return _router.getAmountsOut(_amountIn, _swapData.path)[_swapData.path.length-1];
}
/**
* Gets the input amount of a fixed output swap on Uniswap V2.
*
* @param _swapData the swap parameters
* @param _router the uniswap v2 router address
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV2(
SwapData memory _swapData,
IUniswapV2Router02 _router,
uint256 _amountOut
)
private
view
returns (uint256)
{
return _router.getAmountsIn(_amountOut, _swapData.path)[0];
}
/**
* Gets the output amount of a token swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutAerodrome(
SwapData memory _swapData,
Addresses memory _addresses,
uint256 _amountIn
)
private
returns (uint256)
{
require(_swapData.path.length == 2, "ExchangeIssuance: AERODROME_WRONG_PATH_LENGTH");
return _quoteAerodrome(_amountIn, _swapData.path[0], _swapData.path[1], _addresses);
}
/**
* Gets the input amount of a fixed output swap on Aerodrome
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses.
* @param _amountOut the output amount of the swap
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInAerodrome(
SwapData memory _swapData,
Addresses memory _addresses,
uint256 _amountOut,
uint256 _maxAmountIn
)
private
returns (uint256)
{
require(_swapData.path.length == 2, "ExchangeIssuance: AERODROME_WRONG_PATH_LENGTH");
uint256 firstAmountOut = _quoteAerodrome(_maxAmountIn, _swapData.path[0], _swapData.path[1], _addresses);
// Note: Probably inaccurate since the state of the underlying liquidity would change from the first swap, which is not accounted for here
uint256 amountSwappedBack = _quoteAerodrome(firstAmountOut - _amountOut, _swapData.path[1], _swapData.path[0], _addresses);
return _maxAmountIn - amountSwappedBack;
}
/**
* Gets the output amount of a token swap on Aerodrome Slipstream.
*
* @param _swapData the swap parameters
* @param _quoter the aerodrome slipstream quoter
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutAerodromeSlipstream(
SwapData memory _swapData,
address _quoter,
uint256 _amountIn
)
private
returns (uint256)
{
bytes memory path = _encodePathSlipstream(_swapData.path, _swapData.tickSpacing, false);
(uint256 amountOut,,,) = IAerodromeSlipstreamQuoter(_quoter).quoteExactInput(path, _amountIn);
return amountOut;
}
/**
* Gets the output amount of a token swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter the uniswap v3 quoter
* @param _amountIn the input amount of the trade
*
* @return the output amount of the swap
*/
function _getAmountOutUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountIn
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, false);
return IQuoter(_quoter).quoteExactInput(path, _amountIn);
}
/**
* Gets the input amount of a token swap on AerodromeSlipstream.
*
* @param _swapData the swap parameters
* @param _quoter the aerodrome quoter
* @param _amountOut the output amount of the trade
*
* @return the input amount of the swap
*/
function _getAmountInAerodromeSlipstream(
SwapData memory _swapData,
address _quoter,
uint256 _amountOut
)
private
returns (uint256)
{
bytes memory path = _encodePathSlipstream(_swapData.path, _swapData.tickSpacing, true);
(uint256 amountIn,,,) = IAerodromeSlipstreamQuoter(_quoter).quoteExactOutput(path, _amountOut);
return amountIn;
}
/**
* Gets the input amount of a fixed output swap on Uniswap V3.
*
* @param _swapData the swap parameters
* @param _quoter uniswap v3 quoter
* @param _amountOut the output amount of the swap
*
* @return the input amount of the swap
*/
function _getAmountInUniV3(
SwapData memory _swapData,
address _quoter,
uint256 _amountOut
)
private
returns (uint256)
{
bytes memory path = _encodePathV3(_swapData.path, _swapData.fees, true);
return IQuoter(_quoter).quoteExactOutput(path, _amountOut);
}
/**
* Encode path / tickSpacking to bytes in the format expected by Aerodrome Slipstream router
* @dev Same as _encodePathV3 but with tickSpacing instead of fees
*
* @param _path List of token address to swap via (starting with input token)
* @param _tickSpacing List of tick spacing identifying the pools to swap via.
* (_tickSpacing[0] refers to pool between _path[0] and _path[1])
* @param _reverseOrder Boolean indicating if path needs to be reversed to start with output token.
* (which is the case for exact output swap)
*
* @return encodedPath Encoded path to be forwared to Aerodrome Slipstream router
*/
function _encodePathSlipstream(
address[] memory _path,
int24[] memory _tickSpacing,
bool _reverseOrder
)
private
pure
returns(bytes memory encodedPath)
{
if(_reverseOrder){
encodedPath = abi.encodePacked(_path[_path.length-1]);
for(uint i = 0; i < _tickSpacing.length; i++){
uint index = _tickSpacing.length - i - 1;
encodedPath = abi.encodePacked(encodedPath, _tickSpacing[index], _path[index]);
}
} else {
encodedPath = abi.encodePacked(_path[0]);
for(uint i = 0; i < _tickSpacing.length; i++){
encodedPath = abi.encodePacked(encodedPath, _tickSpacing[i], _path[i+1]);
}
}
}
/**
* Encode path / fees to bytes in the format expected by UniV3 router
*
* @param _path List of token address to swap via (starting with input token)
* @param _fees List of fee levels identifying the pools to swap via.
* (_fees[0] refers to pool between _path[0] and _path[1])
* @param _reverseOrder Boolean indicating if path needs to be reversed to start with output token.
* (which is the case for exact output swap)
*
* @return encodedPath Encoded path to be forwared to uniV3 router
*/
function _encodePathV3(
address[] memory _path,
uint24[] memory _fees,
bool _reverseOrder
)
private
pure
returns(bytes memory encodedPath)
{
if(_reverseOrder){
encodedPath = abi.encodePacked(_path[_path.length-1]);
for(uint i = 0; i < _fees.length; i++){
uint index = _fees.length - i - 1;
encodedPath = abi.encodePacked(encodedPath, _fees[index], _path[index]);
}
} else {
encodedPath = abi.encodePacked(_path[0]);
for(uint i = 0; i < _fees.length; i++){
encodedPath = abi.encodePacked(encodedPath, _fees[i], _path[i+1]);
}
}
}
function _getRouter(
Exchange _exchange,
Addresses memory _addresses
)
private
pure
returns (IUniswapV2Router02)
{
return IUniswapV2Router02(
(_exchange == Exchange.Quickswap) ? _addresses.quickRouter : _addresses.sushiRouter
);
}
/**
* Execute exact input swap via Balancer V2 (supports multihop swaps)
*
* @param _path List of token addresses to swap via.
* @param _amountIn The amount of input token to be spent
* @param _minAmountOut Minimum amount of output token to receive
* @param _poolIds List of pool IDs for each swap step
* @param _vault Address of the Balancer V2 Vault
*
* @return amountOut The amount of output tokens received
*/
function _swapExactTokensForTokensBalancerV2(
address[] memory _path,
uint256 _amountIn,
uint256 _minAmountOut,
bytes32[] memory _poolIds,
IVault _vault
)
private
returns (uint256 amountOut)
{
require(_path.length >= 2, "DEXAdapterV3: BALANCER_PATH_LENGTH");
require(_poolIds.length == _path.length - 1, "DEXAdapterV3: INVALID_POOL_IDS");
// Approve the Vault to spend the input token
_safeApprove(IERC20(_path[0]), address(_vault), _amountIn);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_path.length - 1);
for (uint256 i = 0; i < _path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _poolIds[i],
assetInIndex: _getAssetIndex(assets, _path[i]),
assetOutIndex: _getAssetIndex(assets, _path[i + 1]),
amount: i == 0 ? _amountIn : 0, // Only specify amount for first swap
userData: ""
});
}
// Set up funds
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Set up limits
int256[] memory limits = new int256[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == _path[0]) {
limits[i] = int256(_amountIn);
} else if (assets[i] == _path[_path.length - 1]) {
limits[i] = -int256(_minAmountOut);
} else {
limits[i] = 0;
}
}
// Perform the batch swap
int256[] memory deltas = _vault.batchSwap(
IVault.SwapKind.GIVEN_IN,
swaps,
assets,
funds,
limits,
block.timestamp
);
amountOut = uint256(-deltas[_getAssetIndex(assets, _path[_path.length - 1])]);
require(amountOut >= _minAmountOut, "DEXAdapterV3: INSUFFICIENT_OUTPUT_AMOUNT");
}
/**
* Execute exact output swap via Balancer V2 (supports multihop swaps)
*
* @param _path List of token addresses to swap via.
* @param _amountOut The amount of output token required
* @param _maxAmountIn Maximum amount of input token to be spent
* @param _poolIds List of pool IDs for each swap step
* @param _vault Address of the Balancer V2 Vault
*
* @return amountIn The amount of input tokens spent
*/
function _swapTokensForExactTokensBalancerV2(
address[] memory _path,
uint256 _amountOut,
uint256 _maxAmountIn,
bytes32[] memory _poolIds,
IVault _vault
)
private
returns (uint256 amountIn)
{
require(_path.length >= 2, "DEXAdapterV3: BALANCER_PATH_LENGTH");
require(_poolIds.length == _path.length - 1, "DEXAdapterV3: INVALID_POOL_IDS");
// Approve the Vault to spend the input token
_safeApprove(IERC20(_path[0]), address(_vault), _maxAmountIn);
// Build the assets array (unique tokens in the path)
address[] memory assets = _getAssets(_path);
// Build the swaps array
IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](_path.length - 1);
for (uint256 i = 0; i < _path.length - 1; i++) {
swaps[i] = IVault.BatchSwapStep({
poolId: _poolIds[i],
assetInIndex: _getAssetIndex(assets, _path[i]),
assetOutIndex: _getAssetIndex(assets, _path[i + 1]),
amount: 0, // Amount is determined by the Vault
userData: ""
});
}
// Set up funds
IVault.FundManagement memory funds = IVault.FundManagement({
sender: address(this),
fromInternalBalance: false,
recipient: payable(address(this)),
toInternalBalance: false
});
// Set up limits
int256[] memory limits = new int256[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == _path[0]) {
limits[i] = int256(_maxAmountIn);
} else if (assets[i] == _path[_path.length - 1]) {
limits[i] = -int256(_amountOut);
} else {
limits[i] = 0;
}
}
// Perform the batch swap
int256[] memory deltas = _vault.batchSwap(
IVault.SwapKind.GIVEN_OUT,
swaps,
assets,
funds,
limits,
block.timestamp
);
amountIn = uint256(deltas[_getAssetIndex(assets, _path[0])]);
require(amountIn <= _maxAmountIn, "DEXAdapterV3: EXCESSIVE_INPUT_AMOUNT");
}
/**
* Gets the output amount of a token swap on Balancer V2 using queryBatchSwap.
*
* @param _swapData the swap parameters
* @param _addresses Struct containing relevant smart contract addresses
* @param _amountIn the input amount of the trade
*
* @return amountOut the output amount of the swap
*/
function _getAmountOutBalancer...
// [truncated — 54999 bytes total]
ExchangeIssuance.sol 921 lines
/*
Copyright 2021 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IUniswapV2Factory } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IBasicIssuanceModule } from "../interfaces/IBasicIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
/**
* @title ExchangeIssuance
* @author Index Coop
*
* Contract for issuing and redeeming any SetToken using ETH or an ERC20 as the paying/receiving currency.
* All swaps are done using the best price found on Uniswap or Sushiswap.
*
*/
contract ExchangeIssuance is ReentrancyGuard {
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Enums ============ */
enum Exchange { Uniswap, Sushiswap, None }
/* ============ Constants ============= */
uint256 constant private MAX_UINT96 = 2**96 - 1;
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ State Variables ============ */
address public WETH;
IUniswapV2Router02 public uniRouter;
IUniswapV2Router02 public sushiRouter;
address public immutable uniFactory;
address public immutable sushiFactory;
IController public immutable setController;
IBasicIssuanceModule public immutable basicIssuanceModule;
/* ============ Events ============ */
event ExchangeIssue(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event ExchangeRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
event Refund(
address indexed _recipient, // The recipient address which redeemed the SetTokens
uint256 _refundAmount // The amount of ETH redunded to the recipient
);
/* ============ Modifiers ============ */
modifier isSetToken(ISetToken _setToken) {
require(setController.isSet(address(_setToken)), "ExchangeIssuance: INVALID SET");
_;
}
/* ============ Constructor ============ */
constructor(
address _weth,
address _uniFactory,
IUniswapV2Router02 _uniRouter,
address _sushiFactory,
IUniswapV2Router02 _sushiRouter,
IController _setController,
IBasicIssuanceModule _basicIssuanceModule
)
public
{
uniFactory = _uniFactory;
uniRouter = _uniRouter;
sushiFactory = _sushiFactory;
sushiRouter = _sushiRouter;
setController = _setController;
basicIssuanceModule = _basicIssuanceModule;
WETH = _weth;
IERC20(WETH).safeApprove(address(uniRouter), PreciseUnitMath.maxUint256());
IERC20(WETH).safeApprove(address(sushiRouter), PreciseUnitMath.maxUint256());
}
/* ============ Public Functions ============ */
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) public {
_safeApprove(_token, address(uniRouter), MAX_UINT96);
_safeApprove(_token, address(sushiRouter), MAX_UINT96);
_safeApprove(_token, address(basicIssuanceModule), MAX_UINT96);
}
/* ============ External Functions ============ */
receive() external payable {
// required for weth.withdraw() to work properly
require(msg.sender == WETH, "ExchangeIssuance: Direct deposits not allowed");
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] calldata _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) isSetToken(_setToken) external {
address[] memory components = _setToken.getComponents();
for (uint256 i = 0; i < components.length; i++) {
// Check that the component does not have external positions
require(
_setToken.getExternalPositionModules(components[i]).length == 0,
"ExchangeIssuance: EXTERNAL_POSITIONS_NOT_ALLOWED"
);
approveToken(IERC20(components[i]));
}
}
/**
* Issues SetTokens for an exact amount of input ERC20 tokens.
* The ERC20 token must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken being issued
* @param _inputToken Address of input token
* @param _amountInput Amount of the input token / ether to spend
* @param _minSetReceive Minimum amount of SetTokens to receive. Prevents unnecessary slippage.
*
* @return setTokenAmount Amount of SetTokens issued to the caller
*/
function issueSetForExactToken(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountInput,
uint256 _minSetReceive
)
isSetToken(_setToken)
external
nonReentrant
returns (uint256)
{
require(_amountInput > 0, "ExchangeIssuance: INVALID INPUTS");
_inputToken.safeTransferFrom(msg.sender, address(this), _amountInput);
uint256 amountEth = address(_inputToken) == WETH
? _amountInput
: _swapTokenForWETH(_inputToken, _amountInput);
uint256 setTokenAmount = _issueSetForExactWETH(_setToken, _minSetReceive, amountEth);
emit ExchangeIssue(msg.sender, _setToken, _inputToken, _amountInput, setTokenAmount);
return setTokenAmount;
}
/**
* Issues SetTokens for an exact amount of input ether.
*
* @param _setToken Address of the SetToken to be issued
* @param _minSetReceive Minimum amount of SetTokens to receive. Prevents unnecessary slippage.
*
* @return setTokenAmount Amount of SetTokens issued to the caller
*/
function issueSetForExactETH(
ISetToken _setToken,
uint256 _minSetReceive
)
isSetToken(_setToken)
external
payable
nonReentrant
returns(uint256)
{
require(msg.value > 0, "ExchangeIssuance: INVALID INPUTS");
IWETH(WETH).deposit{value: msg.value}();
uint256 setTokenAmount = _issueSetForExactWETH(_setToken, _minSetReceive, msg.value);
emit ExchangeIssue(msg.sender, _setToken, IERC20(ETH_ADDRESS), msg.value, setTokenAmount);
return setTokenAmount;
}
/**
* Issues an exact amount of SetTokens for given amount of input ERC20 tokens.
* The excess amount of tokens is returned in an equivalent amount of ether.
*
* @param _setToken Address of the SetToken to be issued
* @param _inputToken Address of the input token
* @param _amountSetToken Amount of SetTokens to issue
* @param _maxAmountInputToken Maximum amount of input tokens to be used to issue SetTokens. The unused
* input tokens are returned as ether.
*
* @return amountEthReturn Amount of ether returned to the caller
*/
function issueExactSetFromToken(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken,
uint256 _maxAmountInputToken
)
isSetToken(_setToken)
external
nonReentrant
returns (uint256)
{
require(_amountSetToken > 0 && _maxAmountInputToken > 0, "ExchangeIssuance: INVALID INPUTS");
_inputToken.safeTransferFrom(msg.sender, address(this), _maxAmountInputToken);
uint256 initETHAmount = address(_inputToken) == WETH
? _maxAmountInputToken
: _swapTokenForWETH(_inputToken, _maxAmountInputToken);
uint256 amountEthSpent = _issueExactSetFromWETH(_setToken, _amountSetToken, initETHAmount);
uint256 amountEthReturn = initETHAmount.sub(amountEthSpent);
if (amountEthReturn > 0) {
IWETH(WETH).withdraw(amountEthReturn);
(payable(msg.sender)).sendValue(amountEthReturn);
}
emit Refund(msg.sender, amountEthReturn);
emit ExchangeIssue(msg.sender, _setToken, _inputToken, _maxAmountInputToken, _amountSetToken);
return amountEthReturn;
}
/**
* Issues an exact amount of SetTokens using a given amount of ether.
* The excess ether is returned back.
*
* @param _setToken Address of the SetToken being issued
* @param _amountSetToken Amount of SetTokens to issue
*
* @return amountEthReturn Amount of ether returned to the caller
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _amountSetToken
)
isSetToken(_setToken)
external
payable
nonReentrant
returns (uint256)
{
require(msg.value > 0 && _amountSetToken > 0, "ExchangeIssuance: INVALID INPUTS");
IWETH(WETH).deposit{value: msg.value}();
uint256 amountEth = _issueExactSetFromWETH(_setToken, _amountSetToken, msg.value);
uint256 amountEthReturn = msg.value.sub(amountEth);
if (amountEthReturn > 0) {
IWETH(WETH).withdraw(amountEthReturn);
(payable(msg.sender)).sendValue(amountEthReturn);
}
emit Refund(msg.sender, amountEthReturn);
emit ExchangeIssue(msg.sender, _setToken, IERC20(ETH_ADDRESS), amountEth, _amountSetToken);
return amountEthReturn;
}
/**
* Redeems an exact amount of SetTokens for an ERC20 token.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken being redeemed
* @param _outputToken Address of output token
* @param _amountSetToken Amount SetTokens to redeem
* @param _minOutputReceive Minimum amount of output token to receive
*
* @return outputAmount Amount of output tokens sent to the caller
*/
function redeemExactSetForToken(
ISetToken _setToken,
IERC20 _outputToken,
uint256 _amountSetToken,
uint256 _minOutputReceive
)
isSetToken(_setToken)
external
nonReentrant
returns (uint256)
{
require(_amountSetToken > 0, "ExchangeIssuance: INVALID INPUTS");
address[] memory components = _setToken.getComponents();
(
uint256 totalEth,
uint256[] memory amountComponents,
Exchange[] memory exchanges
) = _getAmountETHForRedemption(_setToken, components, _amountSetToken);
uint256 outputAmount;
if (address(_outputToken) == WETH) {
require(totalEth > _minOutputReceive, "ExchangeIssuance: INSUFFICIENT_OUTPUT_AMOUNT");
_redeemExactSet(_setToken, _amountSetToken);
outputAmount = _liquidateComponentsForWETH(components, amountComponents, exchanges);
} else {
(uint256 totalOutput, Exchange outTokenExchange, ) = _getMaxTokenForExactToken(totalEth, address(WETH), address(_outputToken));
require(totalOutput > _minOutputReceive, "ExchangeIssuance: INSUFFICIENT_OUTPUT_AMOUNT");
_redeemExactSet(_setToken, _amountSetToken);
uint256 outputEth = _liquidateComponentsForWETH(components, amountComponents, exchanges);
outputAmount = _swapExactTokensForTokens(outTokenExchange, WETH, address(_outputToken), outputEth);
}
_outputToken.safeTransfer(msg.sender, outputAmount);
emit ExchangeRedeem(msg.sender, _setToken, _outputToken, _amountSetToken, outputAmount);
return outputAmount;
}
/**
* Redeems an exact amount of SetTokens for ETH.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amountSetToken Amount of SetTokens to redeem
* @param _minEthOut Minimum amount of ETH to receive
*
* @return amountEthOut Amount of ether sent to the caller
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minEthOut
)
isSetToken(_setToken)
external
nonReentrant
returns (uint256)
{
require(_amountSetToken > 0, "ExchangeIssuance: INVALID INPUTS");
address[] memory components = _setToken.getComponents();
(
uint256 totalEth,
uint256[] memory amountComponents,
Exchange[] memory exchanges
) = _getAmountETHForRedemption(_setToken, components, _amountSetToken);
require(totalEth > _minEthOut, "ExchangeIssuance: INSUFFICIENT_OUTPUT_AMOUNT");
_redeemExactSet(_setToken, _amountSetToken);
uint256 amountEthOut = _liquidateComponentsForWETH(components, amountComponents, exchanges);
IWETH(WETH).withdraw(amountEthOut);
(payable(msg.sender)).sendValue(amountEthOut);
emit ExchangeRedeem(msg.sender, _setToken, IERC20(ETH_ADDRESS), _amountSetToken, amountEthOut);
return amountEthOut;
}
/**
* Returns an estimated amount of SetToken that can be issued given an amount of input ERC20 token.
*
* @param _setToken Address of the SetToken being issued
* @param _amountInput Amount of the input token to spend
* @param _inputToken Address of input token.
*
* @return Estimated amount of SetTokens that will be received
*/
function getEstimatedIssueSetAmount(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountInput
)
isSetToken(_setToken)
external
view
returns (uint256)
{
require(_amountInput > 0, "ExchangeIssuance: INVALID INPUTS");
uint256 amountEth;
if (address(_inputToken) != WETH) {
// get max amount of WETH for the `_amountInput` amount of input tokens
(amountEth, , ) = _getMaxTokenForExactToken(_amountInput, address(_inputToken), WETH);
} else {
amountEth = _amountInput;
}
address[] memory components = _setToken.getComponents();
(uint256 setIssueAmount, , ) = _getSetIssueAmountForETH(_setToken, components, amountEth);
return setIssueAmount;
}
/**
* Returns the amount of input ERC20 tokens required to issue an exact amount of SetTokens.
*
* @param _setToken Address of the SetToken being issued
* @param _amountSetToken Amount of SetTokens to issue
*
* @return Amount of tokens needed to issue specified amount of SetTokens
*/
function getAmountInToIssueExactSet(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken
)
isSetToken(_setToken)
external
view
returns(uint256)
{
require(_amountSetToken > 0, "ExchangeIssuance: INVALID INPUTS");
address[] memory components = _setToken.getComponents();
(uint256 totalEth, , , , ) = _getAmountETHForIssuance(_setToken, components, _amountSetToken);
if (address(_inputToken) == WETH) {
return totalEth;
}
(uint256 tokenAmount, , ) = _getMinTokenForExactToken(totalEth, address(_inputToken), address(WETH));
return tokenAmount;
}
/**
* Returns amount of output ERC20 tokens received upon redeeming a given amount of SetToken.
*
* @param _setToken Address of SetToken to be redeemed
* @param _amountSetToken Amount of SetToken to be redeemed
* @param _outputToken Address of output token
*
* @return Estimated amount of ether/erc20 that will be received
*/
function getAmountOutOnRedeemSet(
ISetToken _setToken,
address _outputToken,
uint256 _amountSetToken
)
isSetToken(_setToken)
external
view
returns (uint256)
{
require(_amountSetToken > 0, "ExchangeIssuance: INVALID INPUTS");
address[] memory components = _setToken.getComponents();
(uint256 totalEth, , ) = _getAmountETHForRedemption(_setToken, components, _amountSetToken);
if (_outputToken == WETH) {
return totalEth;
}
// get maximum amount of tokens for totalEth amount of ETH
(uint256 tokenAmount, , ) = _getMaxTokenForExactToken(totalEth, WETH, _outputToken);
return tokenAmount;
}
/* ============ Internal Functions ============ */
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
*/
function _safeApprove(IERC20 _token, address _spender, uint256 _requiredAllowance) internal {
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT96 - allowance);
}
}
/**
* Issues SetTokens for an exact amount of input WETH.
*
* @param _setToken Address of the SetToken being issued
* @param _minSetReceive Minimum amount of index to receive
* @param _totalEthAmount Total amount of WETH to be used to purchase the SetToken components
*
* @return setTokenAmount Amount of SetTokens issued
*/
function _issueSetForExactWETH(ISetToken _setToken, uint256 _minSetReceive, uint256 _totalEthAmount) internal returns (uint256) {
address[] memory components = _setToken.getComponents();
(
uint256 setIssueAmount,
uint256[] memory amountEthIn,
Exchange[] memory exchanges
) = _getSetIssueAmountForETH(_setToken, components, _totalEthAmount);
require(setIssueAmount > _minSetReceive, "ExchangeIssuance: INSUFFICIENT_OUTPUT_AMOUNT");
for (uint256 i = 0; i < components.length; i++) {
_swapExactTokensForTokens(exchanges[i], WETH, components[i], amountEthIn[i]);
}
basicIssuanceModule.issue(_setToken, setIssueAmount, msg.sender);
return setIssueAmount;
}
/**
* Issues an exact amount of SetTokens using WETH.
* Acquires SetToken components at the best price accross uniswap and sushiswap.
* Uses the acquired components to issue the SetTokens.
*
* @param _setToken Address of the SetToken being issued
* @param _amountSetToken Amount of SetTokens to be issued
* @param _maxEther Max amount of ether that can be used to acquire the SetToken components
*
* @return totalEth Total amount of ether used to acquire the SetToken components
*/
function _issueExactSetFromWETH(ISetToken _setToken, uint256 _amountSetToken, uint256 _maxEther) internal returns (uint256) {
address[] memory components = _setToken.getComponents();
(
uint256 sumEth,
,
Exchange[] memory exchanges,
uint256[] memory amountComponents,
) = _getAmountETHForIssuance(_setToken, components, _amountSetToken);
require(sumEth <= _maxEther, "ExchangeIssuance: INSUFFICIENT_INPUT_AMOUNT");
uint256 totalEth = 0;
for (uint256 i = 0; i < components.length; i++) {
uint256 amountEth = _swapTokensForExactTokens(exchanges[i], WETH, components[i], amountComponents[i]);
totalEth = totalEth.add(amountEth);
}
basicIssuanceModule.issue(_setToken, _amountSetToken, msg.sender);
return totalEth;
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
basicIssuanceModule.redeem(_setToken, _amount, address(this));
}
/**
* Liquidates a given list of SetToken components for WETH.
*
* @param _components An array containing the address of SetToken components
* @param _amountComponents An array containing the amount of each SetToken component
* @param _exchanges An array containing the exchange on which to liquidate the SetToken component
*
* @return Total amount of WETH received after liquidating all SetToken components
*/
function _liquidateComponentsForWETH(address[] memory _components, uint256[] memory _amountComponents, Exchange[] memory _exchanges)
internal
returns (uint256)
{
uint256 sumEth = 0;
for (uint256 i = 0; i < _components.length; i++) {
sumEth = _exchanges[i] == Exchange.None
? sumEth.add(_amountComponents[i])
: sumEth.add(_swapExactTokensForTokens(_exchanges[i], _components[i], WETH, _amountComponents[i]));
}
return sumEth;
}
/**
* Gets the total amount of ether required for purchasing each component in a SetToken,
* to enable the issuance of a given amount of SetTokens.
*
* @param _setToken Address of the SetToken to be issued
* @param _components An array containing the addresses of the SetToken components
* @param _amountSetToken Amount of SetToken to be issued
*
* @return sumEth The total amount of Ether reuired to issue the set
* @return amountEthIn An array containing the amount of ether to purchase each component of the SetToken
* @return exchanges An array containing the exchange on which to perform the purchase
* @return amountComponents An array containing the amount of each SetToken component required for issuing the given
* amount of SetToken
* @return pairAddresses An array containing the pair addresses of ETH/component exchange pool
*/
function _getAmountETHForIssuance(ISetToken _setToken, address[] memory _components, uint256 _amountSetToken)
internal
view
returns (
uint256 sumEth,
uint256[] memory amountEthIn,
Exchange[] memory exchanges,
uint256[] memory amountComponents,
address[] memory pairAddresses
)
{
sumEth = 0;
amountEthIn = new uint256[](_components.length);
amountComponents = new uint256[](_components.length);
exchanges = new Exchange[](_components.length);
pairAddresses = new address[](_components.length);
for (uint256 i = 0; i < _components.length; i++) {
// Check that the component does not have external positions
require(
_setToken.getExternalPositionModules(_components[i]).length == 0,
"ExchangeIssuance: EXTERNAL_POSITIONS_NOT_ALLOWED"
);
// Get minimum amount of ETH to be spent to acquire the required amount of SetToken component
uint256 unit = uint256(_setToken.getDefaultPositionRealUnit(_components[i]));
amountComponents[i] = uint256(unit).preciseMulCeil(_amountSetToken);
(amountEthIn[i], exchanges[i], pairAddresses[i]) = _getMinTokenForExactToken(amountComponents[i], WETH, _components[i]);
sumEth = sumEth.add(amountEthIn[i]);
}
return (sumEth, amountEthIn, exchanges, amountComponents, pairAddresses);
}
/**
* Gets the total amount of ether returned from liquidating each component in a SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _components An array containing the addresses of the SetToken components
* @param _amountSetToken Amount of SetToken to be redeemed
*
* @return sumEth The total amount of Ether that would be obtained from liquidating the SetTokens
* @return amountComponents An array containing the amount of SetToken component to be liquidated
* @return exchanges An array containing the exchange on which to liquidate the SetToken components
*/
function _getAmountETHForRedemption(ISetToken _setToken, address[] memory _components, uint256 _amountSetToken)
internal
view
returns (uint256, uint256[] memory, Exchange[] memory)
{
uint256 sumEth = 0;
uint256 amountEth = 0;
uint256[] memory amountComponents = new uint256[](_components.length);
Exchange[] memory exchanges = new Exchange[](_components.length);
for (uint256 i = 0; i < _components.length; i++) {
// Check that the component does not have external positions
require(
_setToken.getExternalPositionModules(_components[i]).length == 0,
"ExchangeIssuance: EXTERNAL_POSITIONS_NOT_ALLOWED"
);
uint256 unit = uint256(_setToken.getDefaultPositionRealUnit(_components[i]));
amountComponents[i] = unit.preciseMul(_amountSetToken);
// get maximum amount of ETH received for a given amount of SetToken component
(amountEth, exchanges[i], ) = _getMaxTokenForExactToken(amountComponents[i], _components[i], WETH);
sumEth = sumEth.add(amountEth);
}
return (sumEth, amountComponents, exchanges);
}
/**
* Returns an estimated amount of SetToken that can be issued given an amount of input ERC20 token.
*
* @param _setToken Address of the SetToken to be issued
* @param _components An array containing the addresses of the SetToken components
* @param _amountEth Total amount of ether available for the purchase of SetToken components
*
* @return setIssueAmount The max amount of SetTokens that can be issued
* @return amountEthIn An array containing the amount ether required to purchase each SetToken component
* @return exchanges An array containing the exchange on which to purchase the SetToken components
*/
function _getSetIssueAmountForETH(ISetToken _setToken, address[] memory _components, uint256 _amountEth)
internal
view
returns (uint256 setIssueAmount, uint256[] memory amountEthIn, Exchange[] memory exchanges)
{
uint256 sumEth;
uint256[] memory unitAmountEthIn;
uint256[] memory unitAmountComponents;
address[] memory pairAddresses;
(
sumEth,
unitAmountEthIn,
exchanges,
unitAmountComponents,
pairAddresses
) = _getAmountETHForIssuance(_setToken, _components, PreciseUnitMath.preciseUnit());
setIssueAmount = PreciseUnitMath.maxUint256();
amountEthIn = new uint256[](_components.length);
for (uint256 i = 0; i < _components.length; i++) {
amountEthIn[i] = unitAmountEthIn[i].mul(_amountEth).div(sumEth);
uint256 amountComponent;
if (exchanges[i] == Exchange.None) {
amountComponent = amountEthIn[i];
} else {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(pairAddresses[i], WETH, _components[i]);
amountComponent = UniSushiV2Library.getAmountOut(amountEthIn[i], reserveIn, reserveOut);
}
setIssueAmount = Math.min(amountComponent.preciseDiv(unitAmountComponents[i]), setIssueAmount);
}
return (setIssueAmount, amountEthIn, exchanges);
}
/**
* Swaps a given amount of an ERC20 token for WETH for the best price on Uniswap/Sushiswap.
*
* @param _token Address of the ERC20 token to be swapped for WETH
* @param _amount Amount of ERC20 token to be swapped
*
* @return Amount of WETH received after the swap
*/
function _swapTokenForWETH(IERC20 _token, uint256 _amount) internal returns (uint256) {
(, Exchange exchange, ) = _getMaxTokenForExactToken(_amount, address(_token), WETH);
IUniswapV2Router02 router = _getRouter(exchange);
_safeApprove(_token, address(router), _amount);
return _swapExactTokensForTokens(exchange, address(_token), WETH, _amount);
}
/**
* Swap exact tokens for another token on a given DEX.
*
* @param _exchange The exchange on which to peform the swap
* @param _tokenIn The address of the input token
* @param _tokenOut The address of the output token
* @param _amountIn The amount of input token to be spent
*
* @return The amount of output tokens
*/
function _swapExactTokensForTokens(Exchange _exchange, address _tokenIn, address _tokenOut, uint256 _amountIn) internal returns (uint256) {
if (_tokenIn == _tokenOut) {
return _amountIn;
}
address[] memory path = new address[](2);
path[0] = _tokenIn;
path[1] = _tokenOut;
return _getRouter(_exchange).swapExactTokensForTokens(_amountIn, 0, path, address(this), block.timestamp)[1];
}
/**
* Swap tokens for exact amount of output tokens on a given DEX.
*
* @param _exchange The exchange on which to peform the swap
* @param _tokenIn The address of the input token
* @param _tokenOut The address of the output token
* @param _amountOut The amount of output token required
*
* @return The amount of input tokens spent
*/
function _swapTokensForExactTokens(Exchange _exchange, address _tokenIn, address _tokenOut, uint256 _amountOut) internal returns (uint256) {
if (_tokenIn == _tokenOut) {
return _amountOut;
}
address[] memory path = new address[](2);
path[0] = _tokenIn;
path[1] = _tokenOut;
return _getRouter(_exchange).swapTokensForExactTokens(_amountOut, PreciseUnitMath.maxUint256(), path, address(this), block.timestamp)[0];
}
/**
* Compares the amount of token required for an exact amount of another token across both exchanges,
* and returns the min amount.
*
* @param _amountOut The amount of output token
* @param _tokenA The address of tokenA
* @param _tokenB The address of tokenB
*
* @return The min amount of tokenA required across both exchanges
* @return The Exchange on which minimum amount of tokenA is required
* @return The pair address of the uniswap/sushiswap pool containing _tokenA and _tokenB
*/
function _getMinTokenForExactToken(uint256 _amountOut, address _tokenA, address _tokenB) internal view returns (uint256, Exchange, address) {
if (_tokenA == _tokenB) {
return (_amountOut, Exchange.None, ETH_ADDRESS);
}
uint256 maxIn = PreciseUnitMath.maxUint256() ;
uint256 uniTokenIn = maxIn;
uint256 sushiTokenIn = maxIn;
address uniswapPair = _getPair(uniFactory, _tokenA, _tokenB);
if (uniswapPair != address(0)) {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(uniswapPair, _tokenA, _tokenB);
// Prevent subtraction overflow by making sure pool reserves are greater than swap amount
if (reserveOut > _amountOut) {
uniTokenIn = UniSushiV2Library.getAmountIn(_amountOut, reserveIn, reserveOut);
}
}
address sushiswapPair = _getPair(sushiFactory, _tokenA, _tokenB);
if (sushiswapPair != address(0)) {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(sushiswapPair, _tokenA, _tokenB);
// Prevent subtraction overflow by making sure pool reserves are greater than swap amount
if (reserveOut > _amountOut) {
sushiTokenIn = UniSushiV2Library.getAmountIn(_amountOut, reserveIn, reserveOut);
}
}
// Fails if both the values are maxIn
require(!(uniTokenIn == maxIn && sushiTokenIn == maxIn), "ExchangeIssuance: ILLIQUID_SET_COMPONENT");
return (uniTokenIn <= sushiTokenIn) ? (uniTokenIn, Exchange.Uniswap, uniswapPair) : (sushiTokenIn, Exchange.Sushiswap, sushiswapPair);
}
/**
* Compares the amount of token received for an exact amount of another token across both exchanges,
* and returns the max amount.
*
* @param _amountIn The amount of input token
* @param _tokenA The address of tokenA
* @param _tokenB The address of tokenB
*
* @return The max amount of tokens that can be received across both exchanges
* @return The Exchange on which maximum amount of token can be received
* @return The pair address of the uniswap/sushiswap pool containing _tokenA and _tokenB
*/
function _getMaxTokenForExactToken(uint256 _amountIn, address _tokenA, address _tokenB) internal view returns (uint256, Exchange, address) {
if (_tokenA == _tokenB) {
return (_amountIn, Exchange.None, ETH_ADDRESS);
}
uint256 uniTokenOut = 0;
uint256 sushiTokenOut = 0;
address uniswapPair = _getPair(uniFactory, _tokenA, _tokenB);
if(uniswapPair != address(0)) {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(uniswapPair, _tokenA, _tokenB);
uniTokenOut = UniSushiV2Library.getAmountOut(_amountIn, reserveIn, reserveOut);
}
address sushiswapPair = _getPair(sushiFactory, _tokenA, _tokenB);
if(sushiswapPair != address(0)) {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(sushiswapPair, _tokenA, _tokenB);
sushiTokenOut = UniSushiV2Library.getAmountOut(_amountIn, reserveIn, reserveOut);
}
// Fails if both the values are 0
require(!(uniTokenOut == 0 && sushiTokenOut == 0), "ExchangeIssuance: ILLIQUID_SET_COMPONENT");
return (uniTokenOut >= sushiTokenOut) ? (uniTokenOut, Exchange.Uniswap, uniswapPair) : (sushiTokenOut, Exchange.Sushiswap, sushiswapPair);
}
/**
* Returns the pair address for on a given DEX.
*
* @param _factory The factory to address
* @param _tokenA The address of tokenA
* @param _tokenB The address of tokenB
*
* @return The pair address (Note: address(0) is returned by default if the pair is not available on that DEX)
*/
function _getPair(address _factory, address _tokenA, address _tokenB) internal view returns (address) {
return IUniswapV2Factory(_factory).getPair(_tokenA, _tokenB);
}
/**
* Returns the router address of a given exchange.
*
* @param _exchange The Exchange whose router address is needed
*
* @return IUniswapV2Router02 router of the given exchange
*/
function _getRouter(Exchange _exchange) internal view returns(IUniswapV2Router02) {
return (_exchange == Exchange.Uniswap) ? uniRouter : sushiRouter;
}
}
ExchangeIssuanceIcEth.sol 464 lines
/*
Copyright 2025 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { IAaveLeverageModule } from "../interfaces/IAaveLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
import { FlashLoanReceiverBaseV2 } from "../../external/contracts/aaveV2/FlashLoanReceiverBaseV2.sol";
import { DEXAdapter } from "./DEXAdapter.sol";
/**
* @title ExchangeIssuanceIcEth
* @author Index Coop
*
* Contract for redeeming deleveraged icETH
*/
contract ExchangeIssuanceIcEth is ReentrancyGuard, FlashLoanReceiverBaseV2{
using DEXAdapter for DEXAdapter.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralAToken;
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapter.SwapData collateralAndDebtSwapData;
DEXAdapter.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IAaveLeverageModule public immutable aaveLeverageModule;
DEXAdapter.Addresses public addresses;
/* ============ Events ============ */
event ExchangeIssue(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event ExchangeRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier onlyLendingPool() {
require(msg.sender == address(LENDING_POOL), "ExchangeIssuance: LENDING POOL ONLY");
_;
}
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapter.ETH_ADDRESS),
"ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapter.ETH_ADDRESS),
"ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _weth Address of wrapped native token
* @param _quickRouter Address of quickswap router
* @param _sushiRouter Address of sushiswap router
* @param _uniV3Router Address of uniswap v3 router
* @param _uniV3Quoter Address of uniswap v3 quoter
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _aaveAddressProvider Address of address provider for aaves addresses
* @param _curveAddressProvider Contract to get current implementation address of curve registry
* @param _curveCalculator Contract to calculate required input to receive given output in curve (for exact output swaps)
*/
constructor(
address _weth,
address _quickRouter,
address _sushiRouter,
address _uniV3Router,
address _uniV3Quoter,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IAaveLeverageModule _aaveLeverageModule,
address _aaveAddressProvider,
address _curveAddressProvider,
address _curveCalculator
)
public
FlashLoanReceiverBaseV2(_aaveAddressProvider)
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
aaveLeverageModule = _aaveLeverageModule;
addresses.weth = _weth;
addresses.quickRouter = _quickRouter;
addresses.sushiRouter = _sushiRouter;
addresses.uniV3Router = _uniV3Router;
addresses.uniV3Quoter = _uniV3Quoter;
addresses.curveAddressProvider = _curveAddressProvider;
addresses.curveCalculator = _curveCalculator;
}
/* ============ External Functions ============ */
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataOutputToken swap data for the collateral token to the output token
*
* @return amount of _outputToken that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory /* _swapDataCollateralForDebt */,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
address[] memory components;
uint256[] memory equityPositions;
(components, equityPositions,) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
address collateralToken;
uint256 collateralAmount;
uint256 wethAmount;
if (components[0] == addresses.weth) {
collateralToken = IAToken(components[1]).UNDERLYING_ASSET_ADDRESS();
collateralAmount = equityPositions[1] + ROUNDING_ERROR_MARGIN;
wethAmount = equityPositions[0];
} else {
collateralToken = IAToken(components[0]).UNDERLYING_ASSET_ADDRESS();
collateralAmount = equityPositions[0] + ROUNDING_ERROR_MARGIN;
wethAmount = equityPositions[1];
}
return DEXAdapter.getAmountOut(addresses, _swapDataOutputToken, collateralAmount).add(wethAmount);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory /* _swapDataCollateralForDebt */,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
nonReentrant
{
aaveLeverageModule.sync(_setToken);
address[] memory components;
uint256[] memory equityPositions;
(components, equityPositions,) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
address collateralToken;
if (components[0] == addresses.weth) {
collateralToken = IAToken(components[1]).UNDERLYING_ASSET_ADDRESS();
} else {
collateralToken = IAToken(components[0]).UNDERLYING_ASSET_ADDRESS();
}
_redeemSet(
_setToken,
_setAmount,
msg.sender
);
_withdrawCollateralToken(
collateralToken,
MAX_UINT256
);
_swapCollateralForOutputToken(
collateralToken,
IERC20(collateralToken).balanceOf(address(this)),
addresses.weth,
_minAmountOutputToken,
_swapDataOutputToken
);
IWETH weth = IWETH(addresses.weth);
uint256 wethBalance = weth.balanceOf(address(this));
weth.withdraw(wethBalance);
uint256 ethBalance = address(this).balance;
(payable(msg.sender)).sendValue(ethBalance);
emit ExchangeRedeem(msg.sender, _setToken, 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, _setAmount, ethBalance);
require(ethBalance >= _minAmountOutputToken, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
}
function executeOperation(
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
address initiator,
bytes memory params
)
external
override
onlyLendingPool
returns (bool)
{
require(1 == 0, "ExchangeIssuanceIcEth: No flash loans");
return true;
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
address[] memory components;
uint256[] memory equityPositions;
(components, equityPositions,) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, 1 ether);
address collateralAToken;
address collateralToken;
if (components[0] == addresses.weth) {
collateralAToken = components[1];
collateralToken = IAToken(components[1]).UNDERLYING_ASSET_ADDRESS();
} else {
collateralAToken = components[0];
collateralToken = IAToken(components[0]).UNDERLYING_ASSET_ADDRESS();
}
_approveToken(IERC20(collateralAToken));
_approveTokenToLendingPool(IERC20(collateralToken));
_approveToken(IERC20(addresses.weth));
_approveTokenToLendingPool(IERC20(addresses.weth));
}
/* ============ Internal Functions ============ */
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Redeems set token using the previously obtained debt token
* Results in collateral token being returned to the contract
*
* @param _setToken Address of the SetToken to be redeemed
* @param _setAmount Amount of SetTokens to redeem
* @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Address of collateral token
* @param _collateralTokenAmount Amount of colalteral token to swap
* @param _outputToken Address of the ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
*
* @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
* @param _collateralAmount Amount of collateralToken to withdraw
*/
function _withdrawCollateralToken(
address _collateralToken,
uint256 _collateralAmount
) internal {
LENDING_POOL.withdraw(_collateralToken, _collateralAmount, address(this));
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Approves max amount of token to lending pool
*
* @param _token Address of the token to approve
*/
function _approveTokenToLendingPool(
IERC20 _token
)
internal
{
uint256 allowance = _token.allowance(address(this), address(LENDING_POOL));
if (allowance > 0) {
_token.approve(address(LENDING_POOL), 0);
}
_token.approve(address(LENDING_POOL), MAX_UINT256);
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
debtIssuanceModule.redeem(_setToken, _amount, address(this));
}
}
ExchangeIssuanceLeveraged.sol 1271 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { IAaveLeverageModule } from "../interfaces/IAaveLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
import { FlashLoanReceiverBaseV2 } from "../../external/contracts/aaveV2/FlashLoanReceiverBaseV2.sol";
import { DEXAdapter } from "./DEXAdapter.sol";
/**
* @title ExchangeIssuance
* @author Index Coop
*
* Contract for issuing and redeeming a leveraged Set Token
* Supports all tokens with one collateral Position in the form of an AToken and one debt position
* Both the collateral as well as the debt token have to be available for flashloand and be
* tradeable against each other on Sushi / Quickswap
*/
contract ExchangeIssuanceLeveraged is ReentrancyGuard, FlashLoanReceiverBaseV2{
using DEXAdapter for DEXAdapter.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralAToken;
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapter.SwapData collateralAndDebtSwapData;
DEXAdapter.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IAaveLeverageModule public immutable aaveLeverageModule;
DEXAdapter.Addresses public addresses;
/* ============ Events ============ */
event ExchangeIssue(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event ExchangeRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier onlyLendingPool() {
require(msg.sender == address(LENDING_POOL), "ExchangeIssuance: LENDING POOL ONLY");
_;
}
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapter.ETH_ADDRESS),
"ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapter.ETH_ADDRESS),
"ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _weth Address of wrapped native token
* @param _quickRouter Address of quickswap router
* @param _sushiRouter Address of sushiswap router
* @param _uniV3Router Address of uniswap v3 router
* @param _uniV3Quoter Address of uniswap v3 quoter
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _aaveAddressProvider Address of address provider for aaves addresses
* @param _curveAddressProvider Contract to get current implementation address of curve registry
* @param _curveCalculator Contract to calculate required input to receive given output in curve (for exact output swaps)
*/
constructor(
address _weth,
address _quickRouter,
address _sushiRouter,
address _uniV3Router,
address _uniV3Quoter,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IAaveLeverageModule _aaveLeverageModule,
address _aaveAddressProvider,
address _curveAddressProvider,
address _curveCalculator
)
public
FlashLoanReceiverBaseV2(_aaveAddressProvider)
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
aaveLeverageModule = _aaveLeverageModule;
addresses.weth = _weth;
addresses.quickRouter = _quickRouter;
addresses.sushiRouter = _sushiRouter;
addresses.uniV3Router = _uniV3Router;
addresses.uniV3Quoter = _uniV3Quoter;
addresses.curveAddressProvider = _curveAddressProvider;
addresses.curveCalculator = _curveCalculator;
}
/* ============ External Functions ============ */
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
external
view
returns (LeveragedTokenData memory)
{
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the input cost of issuing a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataDebtForCollateral swap data for the debt to collateral swap
* @param _swapDataInputToken swap data for the input token to collateral swap
*
* @return the amount of input tokens required to perfrom the issuance
*/
function getIssueExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory issueInfo = _getLeveragedTokenData(_setToken, _setAmount, true);
uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
uint256 borrowSaleProceeds = DEXAdapter.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
collateralOwed = collateralOwed.sub(borrowSaleProceeds);
return DEXAdapter.getAmountIn(addresses, _swapDataInputToken, collateralOwed);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataCollateralForDebt swap data for the collateral to debt swap
* @param _swapDataOutputToken swap data for the collateral token to the output token
*
* @return amount of _outputToken that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory redeemInfo = _getLeveragedTokenData(_setToken, _setAmount, false);
uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
uint256 debtPurchaseCost = DEXAdapter.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed);
uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
return DEXAdapter.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
payable
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* This is the callback function that will be called by the AaveLending Pool after flashloaned tokens have been sent
* to this contract.
* After exiting this function the Lending Pool will attempt to transfer back the loaned tokens + interest. If it fails to do so
* the whole transaction gets reverted
*
* @param assets Addresses of all assets that were borrowed
* @param amounts Amounts that were borrowed
* @param premiums Interest to be paid on top of borrowed amount
* @param initiator Address that initiated the flashloan
* @param params Encoded bytestring of other parameters from the original contract call to be used downstream
*
* @return Boolean indicating success of the operation (fixed to true otherwise the whole transaction would be reverted by lending pool)
*/
function executeOperation(
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
address initiator,
bytes memory params
)
external
override
onlyLendingPool
returns (bool)
{
require(initiator == address(this), "ExchangeIssuance: INVALID FLASHLOAN INITIATOR");
require(assets.length == 1, "ExchangeIssuance: TOO MANY ASSETS");
require(amounts.length == 1, "ExchangeIssuance: TOO MANY AMOUNTS");
require(premiums.length == 1, "ExchangeIssuance: TOO MANY PREMIUMS");
DecodedParams memory decodedParams = abi.decode(params, (DecodedParams));
if(decodedParams.isIssuance){
_performIssuance(assets[0], amounts[0], premiums[0], decodedParams);
} else {
_performRedemption(assets[0], amounts[0], premiums[0], decodedParams);
}
return true;
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true);
_approveToken(IERC20(leveragedTokenData.collateralAToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
}
/* ============ Internal Functions ============ */
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _collateralToken Address of the underlying collateral token that was loaned
* @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
address _collateralToken,
uint256 _collateralTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Deposit collateral token obtained from flashloan to get the respective aToken position required for issuance
_depositCollateralToken(_collateralToken, _collateralTokenAmountNet);
// Issue set using the aToken returned by deposit step
_issueSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
uint amountInputTokenSpent = _obtainCollateralTokens(
_collateralToken,
_collateralTokenAmountNet + _premium,
_decodedParams
);
require(amountInputTokenSpent <= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT INPUT AMOUNT");
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _debtToken Address of the debt token that was loaned
* @param _debtTokenAmountNet Amount of debt token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
address _debtToken,
uint256 _debtTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Redeem set using debt tokens obtained from flashloan
_redeemSet(
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender
);
// Withdraw underlying collateral token from the aToken position returned by redeem step
_withdrawCollateralToken(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - ROUNDING_ERROR_MARGIN
);
// Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
uint256 collateralTokenSpent = _swapCollateralForDebtToken(
_debtTokenAmountNet + _premium,
_debtToken,
_decodedParams.leveragedTokenData.collateralAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Liquidate remaining collateral tokens for the payment token specified by user
uint256 amountOutputToken = _liquidateCollateralTokens(
collateralTokenSpent,
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender,
_decodedParams.paymentToken,
_decodedParams.limitAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
_decodedParams.paymentTokenSwapData
);
require(amountOutputToken >= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(equityPositions[0] == 0 || equityPositions[1] == 0, "ExchangeIssuance: TOO MANY EQUITY POSITIONS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
return LeveragedTokenData(
components[0],
IAToken(components[0]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
components[1],
IAToken(components[1]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true);
address[] memory assets = new address[](1);
assets[0] = leveragedTokenData.collateralToken;
uint[] memory amounts = new uint[](1);
amounts[0] = leveragedTokenData.collateralAmount;
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(assets, amounts, params);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false);
address[] memory assets = new address[](1);
assets[0] = leveragedTokenData.debtToken;
uint[] memory amounts = new uint[](1);
amounts[0] = leveragedTokenData.debtAmount;
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(assets, amounts, params);
}
/**
* Gets rid of the obtained collateral tokens from redemption by either sending them to the user
* directly or converting them to the payment token and sending those out.
*
* @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Address of the user who initiated the redemption
* @param _outputToken Address of token to return to the user
* @param _collateralToken Address of the collateral token to sell
* @param _collateralAmount Amount of collateral token to sell
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapter.SwapData memory _swapData
)
internal
returns (uint256)
{
require(_collateralAmount >= _collateralTokenSpent, "ExchangeIssuance: OVERSPENT COLLATERAL TOKEN");
uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
uint256 outputAmount;
if(_outputToken == DEXAdapter.ETH_ADDRESS){
outputAmount = _liquidateCollateralTokensForETH(
_collateralToken,
amountToReturn,
_originalSender,
_minAmountOutputToken,
_swapData
);
} else {
outputAmount = _liquidateCollateralTokensForERC20(
_collateralToken,
amountToReturn,
_originalSender,
IERC20(_outputToken),
_minAmountOutputToken,
_swapData
);
}
emit ExchangeRedeem(_originalSender, _setToken, _outputToken, _setAmount, outputAmount);
return outputAmount;
}
/**
* Returns the collateralToken directly to the user
*
* @param _collateralToken Address of the the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
*/
function _returnCollateralTokensToSender(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender
)
internal
{
IERC20(_collateralToken).transfer(_originalSender, _collateralRemaining);
}
/**
* Sells the collateral tokens for the selected output ERC20 and returns that to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to Output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
_returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
_outputToken.transfer(_originalSender, outputTokenAmount);
return outputTokenAmount;
}
/**
* Sells the remaining collateral tokens for weth, withdraws that and returns native eth to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the eth to
* @param _minAmountOutputToken Minimum amount of output token to return to user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to eth
*
* @return Amount of eth returned to the user
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
if (ethAmount > 0) {
IWETH(addresses.weth).withdraw(ethAmount);
(payable(_originalSender)).sendValue(ethAmount);
}
return ethAmount;
}
/**
* Obtains the tokens necessary to return the flashloan by swapping the debt tokens obtained
* from issuance and making up the shortfall using the users funds.
*
* @param _collateralToken collateral token to obtain
* @param _amountRequired Amount of collateralToken required to repay the flashloan
* @param _decodedParams Struct containing decoded data from original call passed through via flashloan
*
* @return Amount of input token spent
*/
function _obtainCollateralTokens(
address _collateralToken,
uint256 _amountRequired,
DecodedParams memory _decodedParams
)
internal
returns (uint256)
{
uint collateralTokenObtained = _swapDebtForCollateralToken(
_collateralToken,
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.leveragedTokenData.debtAmount,
_decodedParams.collateralAndDebtSwapData
);
uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
uint amountInputToken;
if(_decodedParams.paymentToken == DEXAdapter.ETH_ADDRESS){
amountInputToken = _makeUpShortfallWithETH(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
} else {
amountInputToken = _makeUpShortfallWithERC20(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
IERC20(_decodedParams.paymentToken),
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
}
emit ExchangeIssue(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountInputToken,
_decodedParams.setAmount
);
return amountInputToken;
}
/**
* Issues set token using the previously obtained collateral token
* Results in debt token being returned to the contract
*
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Adress that initiated the token issuance, which will receive the set tokens
*/
function _issueSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
}
/**
* Redeems set token using the previously obtained debt token
* Results in collateral token being returned to the contract
*
* @param _setToken Address of the SetToken to be redeemed
* @param _setAmount Amount of SetTokens to redeem
* @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
* from swapping the debt tokens from the users address
*
* @param _token Address of the token to transfer from user
* @param _shortfall Collateral token shortfall required to return the flashloan
* @param _originalSender Adress that initiated the token issuance, which is the adresss form which to transfer the tokens
*/
function _transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
internal
{
if(_shortfall>0){
IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
}
}
/**
* Makes up the collateral token shortfall with user specified ERC20 token
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
*
* @return Amount of input token spent
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
_transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
return _collateralTokenShortfall;
} else {
_inputToken.transferFrom(_originalSender, address(this), _maxAmountInputToken);
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
if(amountInputToken < _maxAmountInputToken){
_inputToken.transfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
}
return amountInputToken;
}
}
/**
* Makes up the collateral token shortfall with native eth
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _maxAmountEth Maximum amount of eth to pay
*
* @return Amount of eth spent
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapter.SwapData memory _swapData
)
internal
returns(uint256)
{
IWETH(addresses.weth).deposit{value: _maxAmountEth}();
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
if(_maxAmountEth > amountEth){
uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
IWETH(addresses.weth).withdraw(amountEthReturn);
(payable(_originalSender)).sendValue(amountEthReturn);
}
return amountEth;
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _collateralToken Address of the collateral token buy
* @param _debtToken Address of the debt token to sell
* @param _debtAmount Amount of debt token to sell
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token obtained
*/
function _swapDebtForCollateralToken(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _debtToken, _collateralToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_debtAmount,
// minAmountOut is 0 here since we are going to make up the shortfall with the input token.
// Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
0,
_swapData
);
}
/**
* Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption
*
* @param _debtAmount Amount of debt token to buy
* @param _debtToken Address of debt token
* @param _collateralAmount Amount of collateral token available to spend / used as maxAmountIn parameter
* @param _collateralToken Address of collateral token
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token spent
*/
function _swapCollateralForDebtToken(
uint256 _debtAmount,
address _debtToken,
uint256 _collateralAmount,
address _collateralToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _debtToken)
returns (uint256)
{
return addresses.swapTokensForExactTokens(
_debtAmount,
_collateralAmount,
_swapData
);
}
/**
* Acquires the required amount of collateral tokens by swapping the input tokens
* Does nothing if collateral and input token are indentical
*
* @param _collateralToken Address of collateral token
* @param _amountRequired Remaining amount of collateral token required to repay flashloan, after having swapped debt tokens for collateral
* @param _inputToken Address of input token to swap
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of input token spent
*/
function _swapInputForCollateralToken(
address _collateralToken,
uint256 _amountRequired,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(
_swapData.path,
_inputToken,
_collateralToken
)
returns (uint256)
{
if(_collateralToken == _inputToken) return _amountRequired;
return addresses.swapTokensForExactTokens(
_amountRequired,
_maxAmountInputToken,
_swapData
);
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Address of collateral token
* @param _collateralTokenAmount Amount of colalteral token to swap
* @param _outputToken Address of the ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Deposit collateral to aave to obtain collateralAToken for issuance
*
* @param _collateralToken Address of collateral token
* @param _depositAmount Amount to deposit
*/
function _depositCollateralToken(
address _collateralToken,
uint256 _depositAmount
) internal {
LENDING_POOL.deposit(_collateralToken, _depositAmount, address(this), 0);
}
/**
* Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
*
* @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
* @param _collateralAmount Amount of collateralToken to withdraw
*/
function _withdrawCollateralToken(
address _collateralToken,
uint256 _collateralAmount
) internal {
LENDING_POOL.withdraw(_collateralToken, _collateralAmount, address(this));
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Approves max amount of token to lending pool
*
* @param _token Address of the token to approve
*/
function _approveTokenToLendingPool(
IERC20 _token
)
internal
{
uint256 allowance = _token.allowance(address(this), address(LENDING_POOL));
if (allowance > 0) {
_token.approve(address(LENDING_POOL), 0);
}
_token.approve(address(LENDING_POOL), MAX_UINT256);
}
/**
* Triggers the flashloan from the Lending Pool
*
* @param assets Addresses of tokens to loan
* @param amounts Amounts to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address[] memory assets,
uint256[] memory amounts,
bytes memory params
)
internal
{
address receiverAddress = address(this);
address onBehalfOf = address(this);
uint16 referralCode = 0;
uint256[] me...
// [truncated — 50842 bytes total]
ExchangeIssuanceV2.sol 808 lines
/*
Copyright 2021 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IUniswapV2Factory } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IBasicIssuanceModule } from "../interfaces/IBasicIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
/**
* @title ExchangeIssuance
* @author Index Coop
*
* Contract for issuing and redeeming any SetToken using ETH or an ERC20 as the paying/receiving currency.
* All swaps are done using the best price found on Uniswap or Sushiswap.
*
*/
contract ExchangeIssuanceV2 is ReentrancyGuard {
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Enums ============ */
enum Exchange { Uniswap, Sushiswap, None }
/* ============ Constants ============= */
uint256 constant private MAX_UINT96 = 2**96 - 1;
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ State Variables ============ */
address public WETH;
IUniswapV2Router02 public uniRouter;
IUniswapV2Router02 public sushiRouter;
address public immutable uniFactory;
address public immutable sushiFactory;
IController public immutable setController;
IBasicIssuanceModule public immutable basicIssuanceModule;
/* ============ Events ============ */
event ExchangeIssue(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event ExchangeRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
event Refund(
address indexed _recipient, // The recipient address which redeemed the SetTokens
uint256 _refundAmount // The amount of ETH redunded to the recipient
);
/* ============ Modifiers ============ */
modifier isSetToken(ISetToken _setToken) {
require(setController.isSet(address(_setToken)), "ExchangeIssuance: INVALID SET");
_;
}
/* ============ Constructor ============ */
constructor(
address _weth,
address _uniFactory,
IUniswapV2Router02 _uniRouter,
address _sushiFactory,
IUniswapV2Router02 _sushiRouter,
IController _setController,
IBasicIssuanceModule _basicIssuanceModule
)
public
{
uniFactory = _uniFactory;
uniRouter = _uniRouter;
sushiFactory = _sushiFactory;
sushiRouter = _sushiRouter;
setController = _setController;
basicIssuanceModule = _basicIssuanceModule;
WETH = _weth;
IERC20(WETH).safeApprove(address(uniRouter), PreciseUnitMath.maxUint256());
IERC20(WETH).safeApprove(address(sushiRouter), PreciseUnitMath.maxUint256());
}
/* ============ Public Functions ============ */
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) public {
_safeApprove(_token, address(uniRouter), MAX_UINT96);
_safeApprove(_token, address(sushiRouter), MAX_UINT96);
_safeApprove(_token, address(basicIssuanceModule), MAX_UINT96);
}
/* ============ External Functions ============ */
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] calldata _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) isSetToken(_setToken) external {
address[] memory components = _setToken.getComponents();
for (uint256 i = 0; i < components.length; i++) {
// Check that the component does not have external positions
require(
_setToken.getExternalPositionModules(components[i]).length == 0,
"ExchangeIssuance: EXTERNAL_POSITIONS_NOT_ALLOWED"
);
approveToken(IERC20(components[i]));
}
}
/**
* Issues SetTokens for an exact amount of input ERC20 tokens.
* The ERC20 token must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken being issued
* @param _inputToken Address of input token
* @param _amountInput Amount of the input token / ether to spend
* @param _minSetReceive Minimum amount of SetTokens to receive. Prevents unnecessary slippage.
*
* @return setTokenAmount Amount of SetTokens issued to the caller
*/
function issueSetForExactToken(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountInput,
uint256 _minSetReceive
)
isSetToken(_setToken)
external
nonReentrant
returns (uint256)
{
require(_amountInput > 0, "ExchangeIssuance: INVALID INPUTS");
_inputToken.safeTransferFrom(msg.sender, address(this), _amountInput);
uint256 amountEth = address(_inputToken) == WETH
? _amountInput
: _swapTokenForWETH(_inputToken, _amountInput);
uint256 setTokenAmount = _issueSetForExactWETH(_setToken, _minSetReceive, amountEth);
emit ExchangeIssue(msg.sender, _setToken, _inputToken, _amountInput, setTokenAmount);
return setTokenAmount;
}
/**
* Issues an exact amount of SetTokens for given amount of input ERC20 tokens.
* The excess amount of tokens is returned in an equivalent amount of ether.
*
* @param _setToken Address of the SetToken to be issued
* @param _inputToken Address of the input token
* @param _amountSetToken Amount of SetTokens to issue
* @param _maxAmountInputToken Maximum amount of input tokens to be used to issue SetTokens. The unused
* input tokens are returned as ether.
*
* @return amountEthReturn Amount of ether returned to the caller
*/
function issueExactSetFromToken(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken,
uint256 _maxAmountInputToken
)
isSetToken(_setToken)
external
nonReentrant
returns (uint256)
{
require(_amountSetToken > 0 && _maxAmountInputToken > 0, "ExchangeIssuance: INVALID INPUTS");
_inputToken.safeTransferFrom(msg.sender, address(this), _maxAmountInputToken);
uint256 initETHAmount = address(_inputToken) == WETH
? _maxAmountInputToken
: _swapTokenForWETH(_inputToken, _maxAmountInputToken);
uint256 amountEthSpent = _issueExactSetFromWETH(_setToken, _amountSetToken, initETHAmount);
uint256 amountEthReturn = initETHAmount.sub(amountEthSpent);
if (amountEthReturn > 0) {
IERC20(WETH).safeTransfer(msg.sender, amountEthReturn);
}
emit Refund(msg.sender, amountEthReturn);
emit ExchangeIssue(msg.sender, _setToken, _inputToken, _maxAmountInputToken, _amountSetToken);
return amountEthReturn;
}
/**
* Redeems an exact amount of SetTokens for an ERC20 token.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken being redeemed
* @param _outputToken Address of output token
* @param _amountSetToken Amount SetTokens to redeem
* @param _minOutputReceive Minimum amount of output token to receive
*
* @return outputAmount Amount of output tokens sent to the caller
*/
function redeemExactSetForToken(
ISetToken _setToken,
IERC20 _outputToken,
uint256 _amountSetToken,
uint256 _minOutputReceive
)
isSetToken(_setToken)
external
nonReentrant
returns (uint256)
{
require(_amountSetToken > 0, "ExchangeIssuance: INVALID INPUTS");
address[] memory components = _setToken.getComponents();
(
uint256 totalEth,
uint256[] memory amountComponents,
Exchange[] memory exchanges
) = _getAmountETHForRedemption(_setToken, components, _amountSetToken);
uint256 outputAmount;
if (address(_outputToken) == WETH) {
require(totalEth > _minOutputReceive, "ExchangeIssuance: INSUFFICIENT_OUTPUT_AMOUNT");
_redeemExactSet(_setToken, _amountSetToken);
outputAmount = _liquidateComponentsForWETH(components, amountComponents, exchanges);
} else {
(uint256 totalOutput, Exchange outTokenExchange, ) = _getMaxTokenForExactToken(totalEth, address(WETH), address(_outputToken));
require(totalOutput > _minOutputReceive, "ExchangeIssuance: INSUFFICIENT_OUTPUT_AMOUNT");
_redeemExactSet(_setToken, _amountSetToken);
uint256 outputEth = _liquidateComponentsForWETH(components, amountComponents, exchanges);
outputAmount = _swapExactTokensForTokens(outTokenExchange, WETH, address(_outputToken), outputEth);
}
_outputToken.safeTransfer(msg.sender, outputAmount);
emit ExchangeRedeem(msg.sender, _setToken, _outputToken, _amountSetToken, outputAmount);
return outputAmount;
}
/**
* Returns an estimated amount of SetToken that can be issued given an amount of input ERC20 token.
*
* @param _setToken Address of the SetToken being issued
* @param _amountInput Amount of the input token to spend
* @param _inputToken Address of input token.
*
* @return Estimated amount of SetTokens that will be received
*/
function getEstimatedIssueSetAmount(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountInput
)
isSetToken(_setToken)
external
view
returns (uint256)
{
require(_amountInput > 0, "ExchangeIssuance: INVALID INPUTS");
uint256 amountEth;
if (address(_inputToken) != WETH) {
// get max amount of WETH for the `_amountInput` amount of input tokens
(amountEth, , ) = _getMaxTokenForExactToken(_amountInput, address(_inputToken), WETH);
} else {
amountEth = _amountInput;
}
address[] memory components = _setToken.getComponents();
(uint256 setIssueAmount, , ) = _getSetIssueAmountForETH(_setToken, components, amountEth);
return setIssueAmount;
}
/**
* Returns the amount of input ERC20 tokens required to issue an exact amount of SetTokens.
*
* @param _setToken Address of the SetToken being issued
* @param _amountSetToken Amount of SetTokens to issue
*
* @return Amount of tokens needed to issue specified amount of SetTokens
*/
function getAmountInToIssueExactSet(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken
)
isSetToken(_setToken)
external
view
returns(uint256)
{
require(_amountSetToken > 0, "ExchangeIssuance: INVALID INPUTS");
address[] memory components = _setToken.getComponents();
(uint256 totalEth, , , , ) = _getAmountETHForIssuance(_setToken, components, _amountSetToken);
if (address(_inputToken) == WETH) {
return totalEth;
}
(uint256 tokenAmount, , ) = _getMinTokenForExactToken(totalEth, address(_inputToken), address(WETH));
return tokenAmount;
}
/**
* Returns amount of output ERC20 tokens received upon redeeming a given amount of SetToken.
*
* @param _setToken Address of SetToken to be redeemed
* @param _amountSetToken Amount of SetToken to be redeemed
* @param _outputToken Address of output token
*
* @return Estimated amount of ether/erc20 that will be received
*/
function getAmountOutOnRedeemSet(
ISetToken _setToken,
address _outputToken,
uint256 _amountSetToken
)
isSetToken(_setToken)
external
view
returns (uint256)
{
require(_amountSetToken > 0, "ExchangeIssuance: INVALID INPUTS");
address[] memory components = _setToken.getComponents();
(uint256 totalEth, , ) = _getAmountETHForRedemption(_setToken, components, _amountSetToken);
if (_outputToken == WETH) {
return totalEth;
}
// get maximum amount of tokens for totalEth amount of ETH
(uint256 tokenAmount, , ) = _getMaxTokenForExactToken(totalEth, WETH, _outputToken);
return tokenAmount;
}
/* ============ Internal Functions ============ */
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
*/
function _safeApprove(IERC20 _token, address _spender, uint256 _requiredAllowance) internal {
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT96 - allowance);
}
}
/**
* Issues SetTokens for an exact amount of input WETH.
*
* @param _setToken Address of the SetToken being issued
* @param _minSetReceive Minimum amount of index to receive
* @param _totalEthAmount Total amount of WETH to be used to purchase the SetToken components
*
* @return setTokenAmount Amount of SetTokens issued
*/
function _issueSetForExactWETH(ISetToken _setToken, uint256 _minSetReceive, uint256 _totalEthAmount) internal returns (uint256) {
address[] memory components = _setToken.getComponents();
(
uint256 setIssueAmount,
uint256[] memory amountEthIn,
Exchange[] memory exchanges
) = _getSetIssueAmountForETH(_setToken, components, _totalEthAmount);
require(setIssueAmount > _minSetReceive, "ExchangeIssuance: INSUFFICIENT_OUTPUT_AMOUNT");
for (uint256 i = 0; i < components.length; i++) {
_swapExactTokensForTokens(exchanges[i], WETH, components[i], amountEthIn[i]);
}
basicIssuanceModule.issue(_setToken, setIssueAmount, msg.sender);
return setIssueAmount;
}
/**
* Issues an exact amount of SetTokens using WETH.
* Acquires SetToken components at the best price accross uniswap and sushiswap.
* Uses the acquired components to issue the SetTokens.
*
* @param _setToken Address of the SetToken being issued
* @param _amountSetToken Amount of SetTokens to be issued
* @param _maxEther Max amount of ether that can be used to acquire the SetToken components
*
* @return totalEth Total amount of ether used to acquire the SetToken components
*/
function _issueExactSetFromWETH(ISetToken _setToken, uint256 _amountSetToken, uint256 _maxEther) internal returns (uint256) {
address[] memory components = _setToken.getComponents();
(
uint256 sumEth,
,
Exchange[] memory exchanges,
uint256[] memory amountComponents,
) = _getAmountETHForIssuance(_setToken, components, _amountSetToken);
require(sumEth <= _maxEther, "ExchangeIssuance: INSUFFICIENT_INPUT_AMOUNT");
uint256 totalEth = 0;
for (uint256 i = 0; i < components.length; i++) {
uint256 amountEth = _swapTokensForExactTokens(exchanges[i], WETH, components[i], amountComponents[i]);
totalEth = totalEth.add(amountEth);
}
basicIssuanceModule.issue(_setToken, _amountSetToken, msg.sender);
return totalEth;
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
basicIssuanceModule.redeem(_setToken, _amount, address(this));
}
/**
* Liquidates a given list of SetToken components for WETH.
*
* @param _components An array containing the address of SetToken components
* @param _amountComponents An array containing the amount of each SetToken component
* @param _exchanges An array containing the exchange on which to liquidate the SetToken component
*
* @return Total amount of WETH received after liquidating all SetToken components
*/
function _liquidateComponentsForWETH(address[] memory _components, uint256[] memory _amountComponents, Exchange[] memory _exchanges)
internal
returns (uint256)
{
uint256 sumEth = 0;
for (uint256 i = 0; i < _components.length; i++) {
sumEth = _exchanges[i] == Exchange.None
? sumEth.add(_amountComponents[i])
: sumEth.add(_swapExactTokensForTokens(_exchanges[i], _components[i], WETH, _amountComponents[i]));
}
return sumEth;
}
/**
* Gets the total amount of ether required for purchasing each component in a SetToken,
* to enable the issuance of a given amount of SetTokens.
*
* @param _setToken Address of the SetToken to be issued
* @param _components An array containing the addresses of the SetToken components
* @param _amountSetToken Amount of SetToken to be issued
*
* @return sumEth The total amount of Ether reuired to issue the set
* @return amountEthIn An array containing the amount of ether to purchase each component of the SetToken
* @return exchanges An array containing the exchange on which to perform the purchase
* @return amountComponents An array containing the amount of each SetToken component required for issuing the given
* amount of SetToken
* @return pairAddresses An array containing the pair addresses of ETH/component exchange pool
*/
function _getAmountETHForIssuance(ISetToken _setToken, address[] memory _components, uint256 _amountSetToken)
internal
view
returns (
uint256 sumEth,
uint256[] memory amountEthIn,
Exchange[] memory exchanges,
uint256[] memory amountComponents,
address[] memory pairAddresses
)
{
sumEth = 0;
amountEthIn = new uint256[](_components.length);
amountComponents = new uint256[](_components.length);
exchanges = new Exchange[](_components.length);
pairAddresses = new address[](_components.length);
for (uint256 i = 0; i < _components.length; i++) {
// Check that the component does not have external positions
require(
_setToken.getExternalPositionModules(_components[i]).length == 0,
"ExchangeIssuance: EXTERNAL_POSITIONS_NOT_ALLOWED"
);
// Get minimum amount of ETH to be spent to acquire the required amount of SetToken component
uint256 unit = uint256(_setToken.getDefaultPositionRealUnit(_components[i]));
amountComponents[i] = uint256(unit).preciseMulCeil(_amountSetToken);
(amountEthIn[i], exchanges[i], pairAddresses[i]) = _getMinTokenForExactToken(amountComponents[i], WETH, _components[i]);
sumEth = sumEth.add(amountEthIn[i]);
}
return (sumEth, amountEthIn, exchanges, amountComponents, pairAddresses);
}
/**
* Gets the total amount of ether returned from liquidating each component in a SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _components An array containing the addresses of the SetToken components
* @param _amountSetToken Amount of SetToken to be redeemed
*
* @return sumEth The total amount of Ether that would be obtained from liquidating the SetTokens
* @return amountComponents An array containing the amount of SetToken component to be liquidated
* @return exchanges An array containing the exchange on which to liquidate the SetToken components
*/
function _getAmountETHForRedemption(ISetToken _setToken, address[] memory _components, uint256 _amountSetToken)
internal
view
returns (uint256, uint256[] memory, Exchange[] memory)
{
uint256 sumEth = 0;
uint256 amountEth = 0;
uint256[] memory amountComponents = new uint256[](_components.length);
Exchange[] memory exchanges = new Exchange[](_components.length);
for (uint256 i = 0; i < _components.length; i++) {
// Check that the component does not have external positions
require(
_setToken.getExternalPositionModules(_components[i]).length == 0,
"ExchangeIssuance: EXTERNAL_POSITIONS_NOT_ALLOWED"
);
uint256 unit = uint256(_setToken.getDefaultPositionRealUnit(_components[i]));
amountComponents[i] = unit.preciseMul(_amountSetToken);
// get maximum amount of ETH received for a given amount of SetToken component
(amountEth, exchanges[i], ) = _getMaxTokenForExactToken(amountComponents[i], _components[i], WETH);
sumEth = sumEth.add(amountEth);
}
return (sumEth, amountComponents, exchanges);
}
/**
* Returns an estimated amount of SetToken that can be issued given an amount of input ERC20 token.
*
* @param _setToken Address of the SetToken to be issued
* @param _components An array containing the addresses of the SetToken components
* @param _amountEth Total amount of ether available for the purchase of SetToken components
*
* @return setIssueAmount The max amount of SetTokens that can be issued
* @return amountEthIn An array containing the amount ether required to purchase each SetToken component
* @return exchanges An array containing the exchange on which to purchase the SetToken components
*/
function _getSetIssueAmountForETH(ISetToken _setToken, address[] memory _components, uint256 _amountEth)
internal
view
returns (uint256 setIssueAmount, uint256[] memory amountEthIn, Exchange[] memory exchanges)
{
uint256 sumEth;
uint256[] memory unitAmountEthIn;
uint256[] memory unitAmountComponents;
address[] memory pairAddresses;
(
sumEth,
unitAmountEthIn,
exchanges,
unitAmountComponents,
pairAddresses
) = _getAmountETHForIssuance(_setToken, _components, PreciseUnitMath.preciseUnit());
setIssueAmount = PreciseUnitMath.maxUint256();
amountEthIn = new uint256[](_components.length);
for (uint256 i = 0; i < _components.length; i++) {
amountEthIn[i] = unitAmountEthIn[i].mul(_amountEth).div(sumEth);
uint256 amountComponent;
if (exchanges[i] == Exchange.None) {
amountComponent = amountEthIn[i];
} else {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(pairAddresses[i], WETH, _components[i]);
amountComponent = UniSushiV2Library.getAmountOut(amountEthIn[i], reserveIn, reserveOut);
}
setIssueAmount = Math.min(amountComponent.preciseDiv(unitAmountComponents[i]), setIssueAmount);
}
return (setIssueAmount, amountEthIn, exchanges);
}
/**
* Swaps a given amount of an ERC20 token for WETH for the best price on Uniswap/Sushiswap.
*
* @param _token Address of the ERC20 token to be swapped for WETH
* @param _amount Amount of ERC20 token to be swapped
*
* @return Amount of WETH received after the swap
*/
function _swapTokenForWETH(IERC20 _token, uint256 _amount) internal returns (uint256) {
(, Exchange exchange, ) = _getMaxTokenForExactToken(_amount, address(_token), WETH);
IUniswapV2Router02 router = _getRouter(exchange);
_safeApprove(_token, address(router), _amount);
return _swapExactTokensForTokens(exchange, address(_token), WETH, _amount);
}
/**
* Swap exact tokens for another token on a given DEX.
*
* @param _exchange The exchange on which to peform the swap
* @param _tokenIn The address of the input token
* @param _tokenOut The address of the output token
* @param _amountIn The amount of input token to be spent
*
* @return The amount of output tokens
*/
function _swapExactTokensForTokens(Exchange _exchange, address _tokenIn, address _tokenOut, uint256 _amountIn) internal returns (uint256) {
if (_tokenIn == _tokenOut) {
return _amountIn;
}
address[] memory path = new address[](2);
path[0] = _tokenIn;
path[1] = _tokenOut;
return _getRouter(_exchange).swapExactTokensForTokens(_amountIn, 0, path, address(this), block.timestamp)[1];
}
/**
* Swap tokens for exact amount of output tokens on a given DEX.
*
* @param _exchange The exchange on which to peform the swap
* @param _tokenIn The address of the input token
* @param _tokenOut The address of the output token
* @param _amountOut The amount of output token required
*
* @return The amount of input tokens spent
*/
function _swapTokensForExactTokens(Exchange _exchange, address _tokenIn, address _tokenOut, uint256 _amountOut) internal returns (uint256) {
if (_tokenIn == _tokenOut) {
return _amountOut;
}
address[] memory path = new address[](2);
path[0] = _tokenIn;
path[1] = _tokenOut;
return _getRouter(_exchange).swapTokensForExactTokens(_amountOut, PreciseUnitMath.maxUint256(), path, address(this), block.timestamp)[0];
}
/**
* Compares the amount of token required for an exact amount of another token across both exchanges,
* and returns the min amount.
*
* @param _amountOut The amount of output token
* @param _tokenA The address of tokenA
* @param _tokenB The address of tokenB
*
* @return The min amount of tokenA required across both exchanges
* @return The Exchange on which minimum amount of tokenA is required
* @return The pair address of the uniswap/sushiswap pool containing _tokenA and _tokenB
*/
function _getMinTokenForExactToken(uint256 _amountOut, address _tokenA, address _tokenB) internal view returns (uint256, Exchange, address) {
if (_tokenA == _tokenB) {
return (_amountOut, Exchange.None, ETH_ADDRESS);
}
uint256 maxIn = PreciseUnitMath.maxUint256() ;
uint256 uniTokenIn = maxIn;
uint256 sushiTokenIn = maxIn;
address uniswapPair = _getPair(uniFactory, _tokenA, _tokenB);
if (uniswapPair != address(0)) {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(uniswapPair, _tokenA, _tokenB);
// Prevent subtraction overflow by making sure pool reserves are greater than swap amount
if (reserveOut > _amountOut) {
uniTokenIn = UniSushiV2Library.getAmountIn(_amountOut, reserveIn, reserveOut);
}
}
address sushiswapPair = _getPair(sushiFactory, _tokenA, _tokenB);
if (sushiswapPair != address(0)) {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(sushiswapPair, _tokenA, _tokenB);
// Prevent subtraction overflow by making sure pool reserves are greater than swap amount
if (reserveOut > _amountOut) {
sushiTokenIn = UniSushiV2Library.getAmountIn(_amountOut, reserveIn, reserveOut);
}
}
// Fails if both the values are maxIn
require(!(uniTokenIn == maxIn && sushiTokenIn == maxIn), "ExchangeIssuance: ILLIQUID_SET_COMPONENT");
return (uniTokenIn <= sushiTokenIn) ? (uniTokenIn, Exchange.Uniswap, uniswapPair) : (sushiTokenIn, Exchange.Sushiswap, sushiswapPair);
}
/**
* Compares the amount of token received for an exact amount of another token across both exchanges,
* and returns the max amount.
*
* @param _amountIn The amount of input token
* @param _tokenA The address of tokenA
* @param _tokenB The address of tokenB
*
* @return The max amount of tokens that can be received across both exchanges
* @return The Exchange on which maximum amount of token can be received
* @return The pair address of the uniswap/sushiswap pool containing _tokenA and _tokenB
*/
function _getMaxTokenForExactToken(uint256 _amountIn, address _tokenA, address _tokenB) internal view returns (uint256, Exchange, address) {
if (_tokenA == _tokenB) {
return (_amountIn, Exchange.None, ETH_ADDRESS);
}
uint256 uniTokenOut = 0;
uint256 sushiTokenOut = 0;
address uniswapPair = _getPair(uniFactory, _tokenA, _tokenB);
if(uniswapPair != address(0)) {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(uniswapPair, _tokenA, _tokenB);
uniTokenOut = UniSushiV2Library.getAmountOut(_amountIn, reserveIn, reserveOut);
}
address sushiswapPair = _getPair(sushiFactory, _tokenA, _tokenB);
if(sushiswapPair != address(0)) {
(uint256 reserveIn, uint256 reserveOut) = UniSushiV2Library.getReserves(sushiswapPair, _tokenA, _tokenB);
sushiTokenOut = UniSushiV2Library.getAmountOut(_amountIn, reserveIn, reserveOut);
}
// Fails if both the values are 0
require(!(uniTokenOut == 0 && sushiTokenOut == 0), "ExchangeIssuance: ILLIQUID_SET_COMPONENT");
return (uniTokenOut >= sushiTokenOut) ? (uniTokenOut, Exchange.Uniswap, uniswapPair) : (sushiTokenOut, Exchange.Sushiswap, sushiswapPair);
}
/**
* Returns the pair address for on a given DEX.
*
* @param _factory The factory to address
* @param _tokenA The address of tokenA
* @param _tokenB The address of tokenB
*
* @return The pair address (Note: address(0) is returned by default if the pair is not available on that DEX)
*/
function _getPair(address _factory, address _tokenA, address _tokenB) internal view returns (address) {
return IUniswapV2Factory(_factory).getPair(_tokenA, _tokenB);
}
/**
* Returns the router address of a given exchange.
*
* @param _exchange The Exchange whose router address is needed
*
* @return IUniswapV2Router02 router of the given exchange
*/
function _getRouter(Exchange _exchange) internal view returns(IUniswapV2Router02) {
return (_exchange == Exchange.Uniswap) ? uniRouter : sushiRouter;
}
}
ExchangeIssuanceZeroEx.sol 525 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IBasicIssuanceModule } from "../interfaces/IBasicIssuanceModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
contract ExchangeIssuanceZeroEx is Ownable, ReentrancyGuard {
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
struct IssuanceModuleData {
bool isAllowed;
bool isDebtIssuanceModule;
}
/* ============ Constants ============== */
// Placeholder address to identify ETH where it is treated as if it was an ERC20 token
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ State Variables ============ */
address public immutable WETH;
IController public immutable setController;
address public immutable swapTarget;
/* ============ Events ============ */
event ExchangeIssue(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event ExchangeRedeem(
address indexed _recipient, // The recipient adress of the output tokens obtained for redemption
ISetToken indexed _setToken, // The redeemed SetToken
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier isValidModule(address _issuanceModule) {
require(setController.isModule(_issuanceModule), "ExchangeIssuance: INVALID ISSUANCE MODULE");
_;
}
constructor(
address _weth,
IController _setController,
address _swapTarget
)
public
{
setController = _setController;
WETH = _weth;
swapTarget = _swapTarget;
}
/* ============ External Functions ============ */
/**
* Withdraw slippage to selected address
*
* @param _tokens Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(IERC20[] calldata _tokens, address payable _to) external onlyOwner payable {
for(uint256 i = 0; i < _tokens.length; i++) {
if(address(_tokens[i]) == ETH_ADDRESS){
_to.sendValue(address(this).balance);
}
else{
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
receive() external payable {
// required for weth.withdraw() to work properly
require(msg.sender == WETH, "ExchangeIssuance: Direct deposits not allowed");
}
/* ============ Public Functions ============ */
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
* @param _spender Address of the spender which will be approved to spend token. (Must be a whitlisted issuance module)
*/
function approveToken(IERC20 _token, address _spender) public isValidModule(_spender) {
_safeApprove(_token, _spender, type(uint256).max);
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
* @param _spender Address of the spender which will be approved to spend token. (Must be a whitlisted issuance module)
*/
function approveTokens(IERC20[] calldata _tokens, address _spender) external {
for (uint256 i = 0; i < _tokens.length; i++) {
approveToken(_tokens[i], _spender);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
* @param _issuanceModule Address of the issuance module which will be approved to spend component tokens.
*/
function approveSetToken(ISetToken _setToken, address _issuanceModule) external {
address[] memory components = _setToken.getComponents();
for (uint256 i = 0; i < components.length; i++) {
approveToken(IERC20(components[i]), _issuanceModule);
}
}
/**
* Issues an exact amount of SetTokens for given amount of input ERC20 tokens.
* The excess amount of tokens is returned in an equivalent amount of ether.
*
* @param _setToken Address of the SetToken to be issued
* @param _inputToken Address of the input token
* @param _amountSetToken Amount of SetTokens to issue
* @param _maxAmountInputToken Maximum amount of input tokens to be used to issue SetTokens.
* @param _componentQuotes The encoded 0x transactions to execute
*
* @return totalInputTokenSold Amount of input token spent for issuance
*/
function issueExactSetFromToken(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken,
uint256 _maxAmountInputToken,
bytes[] memory _componentQuotes,
address _issuanceModule,
bool _isDebtIssuance
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
_inputToken.safeTransferFrom(msg.sender, address(this), _maxAmountInputToken);
_safeApprove(_inputToken, swapTarget, _maxAmountInputToken);
uint256 totalInputTokenSold = _buyComponentsForInputToken(_setToken, _amountSetToken, _componentQuotes, _inputToken, _issuanceModule, _isDebtIssuance);
require(totalInputTokenSold <= _maxAmountInputToken, "ExchangeIssuance: OVERSPENT TOKEN");
IBasicIssuanceModule(_issuanceModule).issue(_setToken, _amountSetToken, msg.sender);
_returnExcessInputToken(_inputToken, _maxAmountInputToken, totalInputTokenSold);
emit ExchangeIssue(msg.sender, _setToken, _inputToken, _maxAmountInputToken, _amountSetToken);
return totalInputTokenSold;
}
/**
* Issues an exact amount of SetTokens for given amount of ETH.
* The excess amount of tokens is returned in an equivalent amount of ether.
*
* @param _setToken Address of the SetToken to be issued
* @param _amountSetToken Amount of SetTokens to issue
* @param _componentQuotes The encoded 0x transactions to execute
*
* @return amountEthReturn Amount of ether returned to the caller
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _amountSetToken,
bytes[] memory _componentQuotes,
address _issuanceModule,
bool _isDebtIssuance
)
isValidModule(_issuanceModule)
external
nonReentrant
payable
returns (uint256)
{
require(msg.value > 0, "ExchangeIssuance: NO ETH SENT");
IWETH(WETH).deposit{value: msg.value}();
_safeApprove(IERC20(WETH), swapTarget, msg.value);
uint256 totalEthSold = _buyComponentsForInputToken(_setToken, _amountSetToken, _componentQuotes, IERC20(WETH), _issuanceModule, _isDebtIssuance);
require(totalEthSold <= msg.value, "ExchangeIssuance: OVERSPENT ETH");
IBasicIssuanceModule(_issuanceModule).issue(_setToken, _amountSetToken, msg.sender);
uint256 amountEthReturn = msg.value.sub(totalEthSold);
if (amountEthReturn > 0) {
IWETH(WETH).withdraw(amountEthReturn);
payable(msg.sender).sendValue(amountEthReturn);
}
emit ExchangeIssue(msg.sender, _setToken, IERC20(ETH_ADDRESS), totalEthSold, _amountSetToken);
return amountEthReturn;
}
/**
* Redeems an exact amount of SetTokens for an ERC20 token.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken being redeemed
* @param _outputToken Address of output token
* @param _amountSetToken Amount SetTokens to redeem
* @param _minOutputReceive Minimum amount of output token to receive
* @param _componentQuotes The encoded 0x transactions execute (components -> WETH).
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
*
* @return outputAmount Amount of output tokens sent to the caller
*/
function redeemExactSetForToken(
ISetToken _setToken,
IERC20 _outputToken,
uint256 _amountSetToken,
uint256 _minOutputReceive,
bytes[] memory _componentQuotes,
address _issuanceModule,
bool _isDebtIssuance
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
uint256 outputAmount;
_redeemExactSet(_setToken, _amountSetToken, _issuanceModule);
outputAmount = _sellComponentsForOutputToken(_setToken, _amountSetToken, _componentQuotes, _outputToken, _issuanceModule, _isDebtIssuance);
require(outputAmount >= _minOutputReceive, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
// Transfer sender output token
_outputToken.safeTransfer(msg.sender, outputAmount);
// Emit event
emit ExchangeRedeem(msg.sender, _setToken, _outputToken, _amountSetToken, outputAmount);
// Return output amount
return outputAmount;
}
/**
* Redeems an exact amount of SetTokens for ETH.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken being redeemed
* @param _amountSetToken Amount SetTokens to redeem
* @param _minEthReceive Minimum amount of Eth to receive
* @param _componentQuotes The encoded 0x transactions execute
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
*
* @return outputAmount Amount of output tokens sent to the caller
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minEthReceive,
bytes[] memory _componentQuotes,
address _issuanceModule,
bool _isDebtIssuance
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
_redeemExactSet(_setToken, _amountSetToken, _issuanceModule);
uint ethAmount = _sellComponentsForOutputToken(_setToken, _amountSetToken, _componentQuotes, IERC20(WETH), _issuanceModule, _isDebtIssuance);
require(ethAmount >= _minEthReceive, "ExchangeIssuance: INSUFFICIENT WETH RECEIVED");
IWETH(WETH).withdraw(ethAmount);
(payable(msg.sender)).sendValue(ethAmount);
emit ExchangeRedeem(msg.sender, _setToken, IERC20(ETH_ADDRESS), _amountSetToken, ethAmount);
return ethAmount;
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
*/
function _safeApprove(IERC20 _token, address _spender, uint256 _requiredAllowance) internal {
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, type(uint256).max - allowance);
}
}
/**
* Issues an exact amount of SetTokens using WETH.
* Acquires SetToken components by executing the 0x swaps whose callata is passed in _quotes.
* Uses the acquired components to issue the SetTokens.
*
* @param _setToken Address of the SetToken being issued
* @param _amountSetToken Amount of SetTokens to be issued
* @param _quotes The encoded 0x transaction calldata to execute against the 0x ExchangeProxy
* @param _inputToken Token to use to pay for issuance. Must be the sellToken of the 0x trades.
* @param _issuanceModule Issuance module to use for set token issuance.
*
* @return totalInputTokenSold Total amount of input token spent on this issuance
*/
function _buyComponentsForInputToken(
ISetToken _setToken,
uint256 _amountSetToken,
bytes[] memory _quotes,
IERC20 _inputToken,
address _issuanceModule,
bool _isDebtIssuance
)
internal
returns (uint256 totalInputTokenSold)
{
uint256 componentAmountBought;
(address[] memory components, uint256[] memory componentUnits) = getRequiredIssuanceComponents(_issuanceModule, _isDebtIssuance, _setToken, _amountSetToken);
uint256 inputTokenBalanceBefore = _inputToken.balanceOf(address(this));
for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
uint256 units = componentUnits[i];
// If the component is equal to the input token we don't have to trade
if(component == address(_inputToken)) {
totalInputTokenSold = totalInputTokenSold.add(units);
componentAmountBought = units;
}
else {
uint256 componentBalanceBefore = IERC20(component).balanceOf(address(this));
_fillQuote(_quotes[i]);
uint256 componentBalanceAfter = IERC20(component).balanceOf(address(this));
componentAmountBought = componentBalanceAfter.sub(componentBalanceBefore);
require(componentAmountBought >= units, "ExchangeIssuance: UNDERBOUGHT COMPONENT");
}
}
uint256 inputTokenBalanceAfter = _inputToken.balanceOf(address(this));
totalInputTokenSold = totalInputTokenSold.add(inputTokenBalanceBefore.sub(inputTokenBalanceAfter));
}
/**
* Redeems a given list of SetToken components for given token.
*
* @param _setToken The set token being swapped.
* @param _amountSetToken The amount of set token being swapped.
* @param _swaps An array containing ZeroExSwap swaps.
* @param _outputToken The token for which to sell the index components must be the same as the buyToken that was specified when generating the swaps
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
*
* @return totalOutputTokenBought Total amount of output token received after liquidating all SetToken components
*/
function _sellComponentsForOutputToken(ISetToken _setToken, uint256 _amountSetToken, bytes[] memory _swaps, IERC20 _outputToken, address _issuanceModule, bool _isDebtIssuance)
internal
returns (uint256 totalOutputTokenBought)
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredRedemptionComponents(_issuanceModule, _isDebtIssuance, _setToken, _amountSetToken);
uint256 outputTokenBalanceBefore = _outputToken.balanceOf(address(this));
for (uint256 i = 0; i < _swaps.length; i++) {
uint256 maxAmountSell = componentUnits[i];
uint256 componentAmountSold;
// If the component is equal to the output token we don't have to trade
if(components[i] == address(_outputToken)) {
totalOutputTokenBought = totalOutputTokenBought.add(maxAmountSell);
componentAmountSold = maxAmountSell;
}
else {
_safeApprove(IERC20(components[i]), address(swapTarget), maxAmountSell);
uint256 componentBalanceBefore = IERC20(components[i]).balanceOf(address(this));
_fillQuote(_swaps[i]);
uint256 componentBalanceAfter = IERC20(components[i]).balanceOf(address(this));
componentAmountSold = componentBalanceBefore.sub(componentBalanceAfter);
require(maxAmountSell >= componentAmountSold, "ExchangeIssuance: OVERSOLD COMPONENT");
}
}
uint256 outputTokenBalanceAfter = _outputToken.balanceOf(address(this));
totalOutputTokenBought = totalOutputTokenBought.add(outputTokenBalanceAfter.sub(outputTokenBalanceBefore));
}
/**
* Execute a 0x Swap quote
*
* @param _quote Swap quote as returned by 0x API
*
*/
function _fillQuote(
bytes memory _quote
)
internal
{
(bool success, bytes memory returndata) = swapTarget.call(_quote);
// Forwarding errors including new custom errors
// Taken from: https://ethereum.stackexchange.com/a/111187/73805
if (!success) {
if (returndata.length == 0) revert();
assembly {
revert(add(32, returndata), mload(returndata))
}
}
}
/**
* Transfers given amount of set token from the sender and redeems it for underlying components.
* Obtained component tokens are sent to this contract.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount, address _issuanceModule) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
IBasicIssuanceModule(_issuanceModule).redeem(_setToken, _amount, address(this));
}
/**
* Returns excess input token
*
* @param _inputToken Address of the input token to return
* @param _receivedAmount Amount received by the caller
* @param _spentAmount Amount spent for issuance
*/
function _returnExcessInputToken(IERC20 _inputToken, uint256 _receivedAmount, uint256 _spentAmount) internal {
uint256 amountTokenReturn = _receivedAmount.sub(_spentAmount);
if (amountTokenReturn > 0) {
_inputToken.safeTransfer(msg.sender, amountTokenReturn);
}
}
/**
* Returns component positions required for issuance
*
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
* @param _setToken Set token to issue
* @param _amountSetToken Amount of set token to issue
*/
function getRequiredIssuanceComponents(address _issuanceModule, bool _isDebtIssuance, ISetToken _setToken, uint256 _amountSetToken) public view returns(address[] memory components, uint256[] memory positions) {
if(_isDebtIssuance) {
(components, positions, ) = IDebtIssuanceModule(_issuanceModule).getRequiredComponentIssuanceUnits(_setToken, _amountSetToken);
}
else {
(components, positions) = IBasicIssuanceModule(_issuanceModule).getRequiredComponentUnitsForIssue(_setToken, _amountSetToken);
}
}
/**
* Returns component positions required for Redemption
*
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
* @param _setToken Set token to issue
* @param _amountSetToken Amount of set token to issue
*/
function getRequiredRedemptionComponents(address _issuanceModule, bool _isDebtIssuance, ISetToken _setToken, uint256 _amountSetToken) public view returns(address[] memory components, uint256[] memory positions) {
if(_isDebtIssuance) {
(components, positions, ) = IDebtIssuanceModule(_issuanceModule).getRequiredComponentRedemptionUnits(_setToken, _amountSetToken);
}
else {
components = _setToken.getComponents();
positions = new uint256[](components.length);
for(uint256 i = 0; i < components.length; i++) {
uint256 unit = uint256(_setToken.getDefaultPositionRealUnit(components[i]));
positions[i] = unit.preciseMul(_amountSetToken);
}
}
}
}
FlashMintCompositeZeroEx.sol 540 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IBasicIssuanceModule } from "../interfaces/IBasicIssuanceModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
contract FlashMintCompositeZeroEx is Ownable, ReentrancyGuard {
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
struct IssuanceModuleData {
bool isAllowed;
bool isDebtIssuanceModule;
}
struct SwapData {
address swapTarget;
bytes callData;
}
/* ============ Constants ============== */
// Placeholder address to identify ETH where it is treated as if it was an ERC20 token
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public immutable WETH;
IController public immutable setController;
/* ============ State Variables ============ */
mapping(address => bool) public swapTargetWhitelist;
/* ============ Events ============ */
event ExchangeIssue(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event ExchangeRedeem(
address indexed _recipient, // The recipient adress of the output tokens obtained for redemption
ISetToken indexed _setToken, // The redeemed SetToken
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier isValidModule(address _issuanceModule) {
require(setController.isModule(_issuanceModule), "ExchangeIssuance: INVALID ISSUANCE MODULE");
_;
}
constructor(
address _weth,
IController _setController
)
public
{
setController = _setController;
WETH = _weth;
}
/* ============ External Functions ============ */
/**
* Adds or removes a given swapTarget from the whitelist
* OWNER ONLY
*
* @param _swapTarget Settlement contract to add/remove from whitelist
* @param _isAllowed Boolean indicating wether given contract should be included in the whitelist
*
*/
function setSwapTargetWhitelist(address _swapTarget, bool _isAllowed) external onlyOwner {
swapTargetWhitelist[_swapTarget] = _isAllowed;
}
/**
* Withdraw slippage to selected address
*
* @param _tokens Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(IERC20[] calldata _tokens, address payable _to) external onlyOwner payable {
for(uint256 i = 0; i < _tokens.length; i++) {
if(address(_tokens[i]) == ETH_ADDRESS){
_to.sendValue(address(this).balance);
}
else{
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
receive() external payable {
// required for weth.withdraw() to work properly
require(msg.sender == WETH, "ExchangeIssuance: Direct deposits not allowed");
}
/* ============ Public Functions ============ */
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
* @param _spender Address of the spender which will be approved to spend token. (Must be a whitlisted issuance module)
*/
function approveToken(IERC20 _token, address _spender) public isValidModule(_spender) {
_safeApprove(_token, _spender, type(uint256).max);
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
* @param _spender Address of the spender which will be approved to spend token. (Must be a whitlisted issuance module)
*/
function approveTokens(IERC20[] calldata _tokens, address _spender) external {
for (uint256 i = 0; i < _tokens.length; i++) {
approveToken(_tokens[i], _spender);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
* @param _issuanceModule Address of the issuance module which will be approved to spend component tokens.
*/
function approveSetToken(ISetToken _setToken, address _issuanceModule) external {
address[] memory components = _setToken.getComponents();
for (uint256 i = 0; i < components.length; i++) {
approveToken(IERC20(components[i]), _issuanceModule);
}
}
/**
* Issues an exact amount of SetTokens for given amount of input ERC20 tokens.
* The excess amount of tokens is returned in an equivalent amount of ether.
*
* @param _setToken Address of the SetToken to be issued
* @param _inputToken Address of the input token
* @param _amountSetToken Amount of SetTokens to issue
* @param _maxAmountInputToken Maximum amount of input tokens to be used to issue SetTokens.
* @param _componentQuotes The encoded 0x transactions to execute
*
* @return totalInputTokenSold Amount of input token spent for issuance
*/
function issueExactSetFromToken(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken,
uint256 _maxAmountInputToken,
SwapData[] memory _componentQuotes,
address _issuanceModule,
bool _isDebtIssuance
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
_inputToken.safeTransferFrom(msg.sender, address(this), _maxAmountInputToken);
uint256 totalInputTokenSold = _buyComponentsForInputToken(_setToken, _amountSetToken, _componentQuotes, _inputToken, _issuanceModule, _isDebtIssuance);
require(totalInputTokenSold <= _maxAmountInputToken, "ExchangeIssuance: OVERSPENT TOKEN");
IBasicIssuanceModule(_issuanceModule).issue(_setToken, _amountSetToken, msg.sender);
_returnExcessInputToken(_inputToken, _maxAmountInputToken, totalInputTokenSold);
emit ExchangeIssue(msg.sender, _setToken, _inputToken, _maxAmountInputToken, _amountSetToken);
return totalInputTokenSold;
}
/**
* Issues an exact amount of SetTokens for given amount of ETH.
* The excess amount of tokens is returned in an equivalent amount of ether.
*
* @param _setToken Address of the SetToken to be issued
* @param _amountSetToken Amount of SetTokens to issue
* @param _componentQuotes The encoded 0x transactions to execute
*
* @return amountEthReturn Amount of ether returned to the caller
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _amountSetToken,
SwapData[] memory _componentQuotes,
address _issuanceModule,
bool _isDebtIssuance
)
isValidModule(_issuanceModule)
external
nonReentrant
payable
returns (uint256)
{
require(msg.value > 0, "ExchangeIssuance: NO ETH SENT");
IWETH(WETH).deposit{value: msg.value}();
uint256 totalEthSold = _buyComponentsForInputToken(_setToken, _amountSetToken, _componentQuotes, IERC20(WETH), _issuanceModule, _isDebtIssuance);
require(totalEthSold <= msg.value, "ExchangeIssuance: OVERSPENT ETH");
IBasicIssuanceModule(_issuanceModule).issue(_setToken, _amountSetToken, msg.sender);
uint256 amountEthReturn = msg.value.sub(totalEthSold);
if (amountEthReturn > 0) {
IWETH(WETH).withdraw(amountEthReturn);
payable(msg.sender).sendValue(amountEthReturn);
}
emit ExchangeIssue(msg.sender, _setToken, IERC20(ETH_ADDRESS), totalEthSold, _amountSetToken);
return amountEthReturn;
}
/**
* Redeems an exact amount of SetTokens for an ERC20 token.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken being redeemed
* @param _outputToken Address of output token
* @param _amountSetToken Amount SetTokens to redeem
* @param _minOutputReceive Minimum amount of output token to receive
* @param _componentQuotes The encoded 0x transactions execute (components -> WETH).
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
*
* @return outputAmount Amount of output tokens sent to the caller
*/
function redeemExactSetForToken(
ISetToken _setToken,
IERC20 _outputToken,
uint256 _amountSetToken,
uint256 _minOutputReceive,
SwapData[] memory _componentQuotes,
address _issuanceModule,
bool _isDebtIssuance
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
uint256 outputAmount;
_redeemExactSet(_setToken, _amountSetToken, _issuanceModule);
outputAmount = _sellComponentsForOutputToken(_setToken, _amountSetToken, _componentQuotes, _outputToken, _issuanceModule, _isDebtIssuance);
require(outputAmount >= _minOutputReceive, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
// Transfer sender output token
_outputToken.safeTransfer(msg.sender, outputAmount);
// Emit event
emit ExchangeRedeem(msg.sender, _setToken, _outputToken, _amountSetToken, outputAmount);
// Return output amount
return outputAmount;
}
/**
* Redeems an exact amount of SetTokens for ETH.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken being redeemed
* @param _amountSetToken Amount SetTokens to redeem
* @param _minEthReceive Minimum amount of Eth to receive
* @param _componentQuotes The encoded 0x transactions execute
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
*
* @return outputAmount Amount of output tokens sent to the caller
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minEthReceive,
SwapData[] memory _componentQuotes,
address _issuanceModule,
bool _isDebtIssuance
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
_redeemExactSet(_setToken, _amountSetToken, _issuanceModule);
uint ethAmount = _sellComponentsForOutputToken(_setToken, _amountSetToken, _componentQuotes, IERC20(WETH), _issuanceModule, _isDebtIssuance);
require(ethAmount >= _minEthReceive, "ExchangeIssuance: INSUFFICIENT WETH RECEIVED");
IWETH(WETH).withdraw(ethAmount);
(payable(msg.sender)).sendValue(ethAmount);
emit ExchangeRedeem(msg.sender, _setToken, IERC20(ETH_ADDRESS), _amountSetToken, ethAmount);
return ethAmount;
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
*/
function _safeApprove(IERC20 _token, address _spender, uint256 _requiredAllowance) internal {
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, type(uint256).max - allowance);
}
}
/**
* Issues an exact amount of SetTokens using WETH.
* Acquires SetToken components by executing the 0x swaps whose callata is passed in _quotes.
* Uses the acquired components to issue the SetTokens.
*
* @param _setToken Address of the SetToken being issued
* @param _amountSetToken Amount of SetTokens to be issued
* @param _quotes The encoded 0x transaction calldata to execute against the 0x ExchangeProxy
* @param _inputToken Token to use to pay for issuance. Must be the sellToken of the 0x trades.
* @param _issuanceModule Issuance module to use for set token issuance.
*
* @return totalInputTokenSold Total amount of input token spent on this issuance
*/
function _buyComponentsForInputToken(
ISetToken _setToken,
uint256 _amountSetToken,
SwapData[] memory _quotes,
IERC20 _inputToken,
address _issuanceModule,
bool _isDebtIssuance
)
internal
returns (uint256 totalInputTokenSold)
{
uint256 componentAmountBought;
(address[] memory components, uint256[] memory componentUnits) = getRequiredIssuanceComponents(_issuanceModule, _isDebtIssuance, _setToken, _amountSetToken);
uint256 inputTokenBalanceBefore = _inputToken.balanceOf(address(this));
for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
uint256 units = componentUnits[i];
// If the component is equal to the input token we don't have to trade
if(component == address(_inputToken)) {
totalInputTokenSold = totalInputTokenSold.add(units);
componentAmountBought = units;
}
else {
uint256 componentBalanceBefore = IERC20(component).balanceOf(address(this));
_safeApprove(_inputToken, address(_quotes[i].swapTarget), inputTokenBalanceBefore);
_fillQuote(_quotes[i]);
uint256 componentBalanceAfter = IERC20(component).balanceOf(address(this));
componentAmountBought = componentBalanceAfter.sub(componentBalanceBefore);
require(componentAmountBought >= units, "ExchangeIssuance: UNDERBOUGHT COMPONENT");
}
}
uint256 inputTokenBalanceAfter = _inputToken.balanceOf(address(this));
totalInputTokenSold = totalInputTokenSold.add(inputTokenBalanceBefore.sub(inputTokenBalanceAfter));
}
/**
* Redeems a given list of SetToken components for given token.
*
* @param _setToken The set token being swapped.
* @param _amountSetToken The amount of set token being swapped.
* @param _swaps An array containing ZeroExSwap swaps.
* @param _outputToken The token for which to sell the index components must be the same as the buyToken that was specified when generating the swaps
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
*
* @return totalOutputTokenBought Total amount of output token received after liquidating all SetToken components
*/
function _sellComponentsForOutputToken(ISetToken _setToken, uint256 _amountSetToken, SwapData[] memory _swaps, IERC20 _outputToken, address _issuanceModule, bool _isDebtIssuance)
internal
returns (uint256 totalOutputTokenBought)
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredRedemptionComponents(_issuanceModule, _isDebtIssuance, _setToken, _amountSetToken);
uint256 outputTokenBalanceBefore = _outputToken.balanceOf(address(this));
for (uint256 i = 0; i < _swaps.length; i++) {
uint256 maxAmountSell = componentUnits[i];
uint256 componentAmountSold;
// If the component is equal to the output token we don't have to trade
if(components[i] == address(_outputToken)) {
totalOutputTokenBought = totalOutputTokenBought.add(maxAmountSell);
componentAmountSold = maxAmountSell;
}
else {
_safeApprove(IERC20(components[i]), address(_swaps[i].swapTarget), maxAmountSell);
uint256 componentBalanceBefore = IERC20(components[i]).balanceOf(address(this));
_fillQuote(_swaps[i]);
uint256 componentBalanceAfter = IERC20(components[i]).balanceOf(address(this));
componentAmountSold = componentBalanceBefore.sub(componentBalanceAfter);
require(maxAmountSell >= componentAmountSold, "ExchangeIssuance: OVERSOLD COMPONENT");
}
}
uint256 outputTokenBalanceAfter = _outputToken.balanceOf(address(this));
totalOutputTokenBought = totalOutputTokenBought.add(outputTokenBalanceAfter.sub(outputTokenBalanceBefore));
}
/**
* Execute a 0x Swap quote
*
* @param _quote Swap quote as returned by 0x API
*
*/
function _fillQuote(
SwapData memory _quote
)
internal
{
require(swapTargetWhitelist[_quote.swapTarget], "SwapTarget not whitelisted");
(bool success, bytes memory returndata) = _quote.swapTarget.call(_quote.callData);
// Forwarding errors including new custom errors
// Taken from: https://ethereum.stackexchange.com/a/111187/73805
if (!success) {
if (returndata.length == 0) revert();
assembly {
revert(add(32, returndata), mload(returndata))
}
}
}
/**
* Transfers given amount of set token from the sender and redeems it for underlying components.
* Obtained component tokens are sent to this contract.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount, address _issuanceModule) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
IBasicIssuanceModule(_issuanceModule).redeem(_setToken, _amount, address(this));
}
/**
* Returns excess input token
*
* @param _inputToken Address of the input token to return
* @param _receivedAmount Amount received by the caller
* @param _spentAmount Amount spent for issuance
*/
function _returnExcessInputToken(IERC20 _inputToken, uint256 _receivedAmount, uint256 _spentAmount) internal {
uint256 amountTokenReturn = _receivedAmount.sub(_spentAmount);
if (amountTokenReturn > 0) {
_inputToken.safeTransfer(msg.sender, amountTokenReturn);
}
}
/**
* Returns component positions required for issuance
*
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
* @param _setToken Set token to issue
* @param _amountSetToken Amount of set token to issue
*/
function getRequiredIssuanceComponents(address _issuanceModule, bool _isDebtIssuance, ISetToken _setToken, uint256 _amountSetToken) public view returns(address[] memory components, uint256[] memory positions) {
if(_isDebtIssuance) {
(components, positions, ) = IDebtIssuanceModule(_issuanceModule).getRequiredComponentIssuanceUnits(_setToken, _amountSetToken);
}
else {
(components, positions) = IBasicIssuanceModule(_issuanceModule).getRequiredComponentUnitsForIssue(_setToken, _amountSetToken);
}
}
/**
* Returns component positions required for Redemption
*
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
* @param _setToken Set token to issue
* @param _amountSetToken Amount of set token to issue
*/
function getRequiredRedemptionComponents(address _issuanceModule, bool _isDebtIssuance, ISetToken _setToken, uint256 _amountSetToken) public view returns(address[] memory components, uint256[] memory positions) {
if(_isDebtIssuance) {
(components, positions, ) = IDebtIssuanceModule(_issuanceModule).getRequiredComponentRedemptionUnits(_setToken, _amountSetToken);
}
else {
components = _setToken.getComponents();
positions = new uint256[](components.length);
for(uint256 i = 0; i < components.length; i++) {
uint256 unit = uint256(_setToken.getDefaultPositionRealUnit(components[i]));
positions[i] = unit.preciseMul(_amountSetToken);
}
}
}
}
FlashMintDex.sol 611 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IBasicIssuanceModule } from "../interfaces/IBasicIssuanceModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { DEXAdapterV3 } from "./DEXAdapterV3.sol";
/**
* @title FlashMintDex
* @author Index Cooperative
* @notice Part of a family of contracts that allows users to issue and redeem SetTokens with a single input/output token (ETH/ERC20).
* This contract supports SetTokens whose components have liquidity against WETH on the exchanges found in the DEXAdapterV2 library, and
* does not depend on the use of off-chain APIs for swap quotes.
* The FlashMint SDK (https://github.com/IndexCoop/flash-mint-sdk) provides a unified interface for this and other FlashMint contracts.
*/
contract FlashMintDex is Ownable, ReentrancyGuard {
using DEXAdapterV3 for DEXAdapterV3.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Constants ============== */
// Placeholder address to identify ETH where it is treated as if it was an ERC20 token
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ State Variables ============ */
address public immutable WETH;
IController public immutable setController;
IController public immutable indexController;
DEXAdapterV3.Addresses public dexAdapter;
/* ============ Structs ============ */
struct IssueRedeemParams {
ISetToken setToken; // The address of the SetToken to be issued/redeemed
uint256 amountSetToken; // The amount of SetTokens to issue/redeem
DEXAdapterV3.SwapData[] componentSwapData; // The swap data from WETH to each component token
address issuanceModule; // The address of the issuance module to be used
bool isDebtIssuance; // A flag indicating whether the issuance module is a debt issuance module
}
struct PaymentInfo {
IERC20 token; // The address of the input/output token for issuance/redemption
uint256 limitAmt; // Max/min amount of payment token spent/received
DEXAdapterV3.SwapData swapDataTokenToWeth; // The swap data from payment token to WETH
DEXAdapterV3.SwapData swapDataWethToToken; // The swap data from WETH back to payment token
}
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient adress of the output tokens obtained for redemption
ISetToken indexed _setToken, // The redeemed SetToken
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier isValidModule(address _issuanceModule) {
require(setController.isModule(_issuanceModule) || indexController.isModule(_issuanceModule), "FlashMint: INVALID ISSUANCE MODULE");
_;
}
modifier isValidModuleAndSet(address _issuanceModule, address _setToken) {
require(
setController.isModule(_issuanceModule) && setController.isSet(_setToken) ||
indexController.isModule(_issuanceModule) && indexController.isSet(_setToken),
"FlashMint: INVALID ISSUANCE MODULE OR SET TOKEN"
);
_;
}
/**
* Initializes the contract with controller and DEXAdapterV2 library addresses.
*
* @param _setController Address of the legacy Set Protocol controller contract
* @param _indexController Address of the Index Coop controller contract
* @param _dexAddresses Struct containing addresses for the DEXAdapterV2 library
*/
constructor(
IController _setController,
IController _indexController,
DEXAdapterV3.Addresses memory _dexAddresses
)
public
{
setController = _setController;
indexController = _indexController;
dexAdapter = _dexAddresses;
WETH = _dexAddresses.weth;
}
/* ============ External Functions ============ */
/**
* Withdraw slippage to selected address
*
* @param _tokens Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(IERC20[] calldata _tokens, address payable _to) external onlyOwner payable {
for(uint256 i = 0; i < _tokens.length; i++) {
if(address(_tokens[i]) == ETH_ADDRESS){
_to.sendValue(address(this).balance);
}
else{
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
receive() external payable {
// required for weth.withdraw() to work properly
require(msg.sender == WETH, "FlashMint: DIRECT DEPOSITS NOT ALLOWED");
}
/* ============ Public Functions ============ */
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
* @param _spender Address of the spender which will be approved to spend token. (Must be a whitlisted issuance module)
*/
function approveToken(IERC20 _token, address _spender) public isValidModule(_spender) {
_safeApprove(_token, _spender, type(uint256).max);
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
* @param _spender Address of the spender which will be approved to spend token. (Must be a whitlisted issuance module)
*/
function approveTokens(IERC20[] calldata _tokens, address _spender) external {
for (uint256 i = 0; i < _tokens.length; i++) {
approveToken(_tokens[i], _spender);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
* @param _issuanceModule Address of the issuance module which will be approved to spend component tokens.
*/
function approveSetToken(ISetToken _setToken, address _issuanceModule) external {
address[] memory components = _setToken.getComponents();
for (uint256 i = 0; i < components.length; i++) {
approveToken(IERC20(components[i]), _issuanceModule);
}
}
/**
* Gets the amount of input token required to issue a given quantity of set token with the provided issuance params.
* This function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter contract
*
* @param _issueParams Struct containing addresses, amounts, and swap data for issuance
* @param _swapDataInputTokenToWeth Swap data to trade input token for WETH. Use empty swap data if input token is ETH or WETH.
*
* @return Amount of input tokens required to perform the issuance
*/
function getIssueExactSet(
IssueRedeemParams memory _issueParams,
DEXAdapterV3.SwapData memory _swapDataInputTokenToWeth
)
external
returns (uint256)
{
uint256 totalWethNeeded = _getWethCostsForIssue(_issueParams);
return dexAdapter.getAmountIn(_swapDataInputTokenToWeth, totalWethNeeded);
}
/**
* Gets the amount of specified payment token expected to be received after redeeming
* a given quantity of set token with the provided redemption params.
* This function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter contract
*
* @param _redeemParams Struct containing addresses, amounts, and swap data for redemption
* @param _swapDataWethToOutputToken Swap data to trade WETH for output token. Use empty swap data if output token is ETH or WETH.
*
* @return Amount of output tokens expected after performing redemption
*/
function getRedeemExactSet(
IssueRedeemParams memory _redeemParams,
DEXAdapterV3.SwapData memory _swapDataWethToOutputToken
)
external
returns (uint256)
{
uint256 totalWethReceived = _getWethReceivedForRedeem(_redeemParams);
return dexAdapter.getAmountOut(_swapDataWethToOutputToken, totalWethReceived);
}
/**
* Issues an exact amount of SetTokens for given amount of ETH.
* Leftover ETH is returned to the caller if the amount is above _minEthRefund,
* otherwise it is kept by the contract in the form of WETH to save gas.
*
* @param _issueParams Struct containing addresses, amounts, and swap data for issuance
* @param _minEthRefund Minimum amount of unused ETH to be returned to the caller. Set to 0 to return any leftover amount.
*
* @return ethSpent Amount of ETH spent
*/
function issueExactSetFromETH(IssueRedeemParams memory _issueParams, uint256 _minEthRefund)
external
payable
isValidModuleAndSet(_issueParams.issuanceModule, address(_issueParams.setToken))
nonReentrant
returns (uint256 ethSpent)
{
require(msg.value > 0, "FlashMint: NO ETH SENT");
IWETH(WETH).deposit{value: msg.value}();
uint256 ethUsedForIssuance = _issueExactSetFromWeth(_issueParams);
uint256 leftoverETH = msg.value.sub(ethUsedForIssuance);
if (leftoverETH > _minEthRefund) {
IWETH(WETH).withdraw(leftoverETH);
payable(msg.sender).sendValue(leftoverETH);
}
ethSpent = msg.value.sub(leftoverETH);
emit FlashMint(msg.sender, _issueParams.setToken, IERC20(ETH_ADDRESS), ethSpent, _issueParams.amountSetToken);
}
/**
* Issues an exact amount of SetTokens for given amount of input ERC20 tokens.
* Leftover funds are swapped back to the payment token and returned to the caller if the value is above _minRefundValueInWeth,
* otherwise the leftover funds are kept by the contract in the form of WETH to save gas.
*
* @param _issueParams Struct containing addresses, amounts, and swap data for issuance
* @param _paymentInfo Struct containing input token address, max amount to spend, and swap data to trade for WETH
* @param _minRefundValueInWeth Minimum value of leftover WETH to be swapped back to input token and returned to the caller. Set to 0 to return any leftover amount.
*
* @return paymentTokenSpent Amount of input token spent
*/
function issueExactSetFromERC20(IssueRedeemParams memory _issueParams, PaymentInfo memory _paymentInfo, uint256 _minRefundValueInWeth)
external
isValidModuleAndSet(_issueParams.issuanceModule, address(_issueParams.setToken))
nonReentrant
returns (uint256 paymentTokenSpent)
{
_paymentInfo.token.safeTransferFrom(msg.sender, address(this), _paymentInfo.limitAmt);
uint256 wethReceived = _swapPaymentTokenForWeth(_paymentInfo.token, _paymentInfo.limitAmt, _paymentInfo.swapDataTokenToWeth);
uint256 wethSpent = _issueExactSetFromWeth(_issueParams);
require(wethSpent <= wethReceived, "FlashMint: OVERSPENT WETH");
uint256 leftoverWeth = wethReceived.sub(wethSpent);
uint256 paymentTokenReturned = 0;
if (leftoverWeth > _minRefundValueInWeth) {
paymentTokenReturned = _swapWethForPaymentToken(leftoverWeth, _paymentInfo.token, _paymentInfo.swapDataWethToToken);
_paymentInfo.token.safeTransfer(msg.sender, paymentTokenReturned);
}
paymentTokenSpent = _paymentInfo.limitAmt.sub(paymentTokenReturned);
emit FlashMint(msg.sender, _issueParams.setToken, _paymentInfo.token, paymentTokenSpent, _issueParams.amountSetToken);
}
/**
* Redeems an exact amount of SetTokens for ETH.
* The SetToken must be approved by the sender to this contract.
*
* @param _redeemParams Struct containing addresses, amounts, and swap data for issuance
*
* @return ethReceived Amount of ETH received
*/
function redeemExactSetForETH(IssueRedeemParams memory _redeemParams, uint256 _minEthReceive)
external
isValidModuleAndSet(_redeemParams.issuanceModule, address(_redeemParams.setToken))
nonReentrant
returns (uint256 ethReceived)
{
_redeem(_redeemParams.setToken, _redeemParams.amountSetToken, _redeemParams.issuanceModule);
ethReceived = _sellComponentsForWeth(_redeemParams);
require(ethReceived >= _minEthReceive, "FlashMint: INSUFFICIENT WETH RECEIVED");
IWETH(WETH).withdraw(ethReceived);
payable(msg.sender).sendValue(ethReceived);
emit FlashRedeem(msg.sender, _redeemParams.setToken, IERC20(ETH_ADDRESS), _redeemParams.amountSetToken, ethReceived);
return ethReceived;
}
/**
* Redeems an exact amount of SetTokens for an ERC20 token.
* The SetToken must be approved by the sender to this contract.
*
* @param _redeemParams Struct containing token addresses, amounts, and swap data for issuance
*
* @return outputTokenReceived Amount of output token received
*/
function redeemExactSetForERC20(IssueRedeemParams memory _redeemParams, PaymentInfo memory _paymentInfo)
external
isValidModuleAndSet(_redeemParams.issuanceModule, address(_redeemParams.setToken))
nonReentrant
returns (uint256 outputTokenReceived)
{
_redeem(_redeemParams.setToken, _redeemParams.amountSetToken, _redeemParams.issuanceModule);
uint256 wethReceived = _sellComponentsForWeth(_redeemParams);
outputTokenReceived = _swapWethForPaymentToken(wethReceived, _paymentInfo.token, _paymentInfo.swapDataWethToToken);
require(outputTokenReceived >= _paymentInfo.limitAmt, "FlashMint: INSUFFICIENT OUTPUT AMOUNT");
_paymentInfo.token.safeTransfer(msg.sender, outputTokenReceived);
emit FlashRedeem(msg.sender, _redeemParams.setToken, _paymentInfo.token, _redeemParams.amountSetToken, outputTokenReceived);
}
/* ============ Internal Functions ============ */
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
*/
function _safeApprove(IERC20 _token, address _spender, uint256 _requiredAllowance) internal {
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, type(uint256).max - allowance);
}
}
/**
* Swaps a given amount of an ERC20 token for WETH using the DEXAdapterV3.
*
* @param _paymentToken Address of the ERC20 payment token
* @param _paymentTokenAmount Amount of payment token to swap
* @param _swapData Swap data from input token to WETH
*
* @return amountWethOut Amount of WETH received after the swap
*/
function _swapPaymentTokenForWeth(
IERC20 _paymentToken,
uint256 _paymentTokenAmount,
DEXAdapterV3.SwapData memory _swapData
)
internal
returns (uint256 amountWethOut)
{
if (_paymentToken == IERC20(WETH)) {
return _paymentTokenAmount;
}
return dexAdapter.swapExactTokensForTokens(
_paymentTokenAmount,
0,
_swapData
);
}
/**
* Swaps a given amount of an WETH for ERC20 using the DEXAdapterV3.
*
* @param _wethAmount Amount of WETH to swap for input token
* @param _paymentToken Address of the input token
* @param _swapData Swap data from WETH to input token
*
* @return amountOut Amount of ERC20 received after the swap
*/
function _swapWethForPaymentToken(uint256 _wethAmount, IERC20 _paymentToken, DEXAdapterV3.SwapData memory _swapData)
internal
returns (uint256 amountOut)
{
// If the payment token is equal to WETH we don't have to trade
if (_paymentToken == IERC20(WETH)) {
return _wethAmount;
}
return dexAdapter.swapExactTokensForTokens(
_wethAmount,
0,
_swapData
);
}
/**
* Issues an exact amount of SetTokens for given amount of WETH.
*
* @param _issueParams Struct containing addresses, amounts, and swap data for issuance
*
* @return totalWethSpent Amount of WETH used to buy components
*/
function _issueExactSetFromWeth(IssueRedeemParams memory _issueParams) internal returns (uint256 totalWethSpent)
{
totalWethSpent = _buyComponentsWithWeth(_issueParams);
IBasicIssuanceModule(_issueParams.issuanceModule).issue(_issueParams.setToken, _issueParams.amountSetToken, msg.sender);
}
/**
* Acquires SetToken components by executing swaps whose callata is passed in _componentSwapData.
* Acquired components are then used to issue the SetTokens.
*
* @param _issueParams Struct containing addresses, amounts, and swap data for issuance
*
* @return totalWethSpent Total amount of WETH spent to buy components
*/
function _buyComponentsWithWeth(IssueRedeemParams memory _issueParams) internal returns (uint256 totalWethSpent) {
(address[] memory components, uint256[] memory componentUnits) = getRequiredIssuanceComponents(
_issueParams.issuanceModule,
_issueParams.isDebtIssuance,
_issueParams.setToken,
_issueParams.amountSetToken
);
require(components.length == _issueParams.componentSwapData.length, "FlashMint: INVALID NUMBER OF COMPONENTS IN SWAP DATA");
totalWethSpent = 0;
for (uint256 i = 0; i < components.length; i++) {
require(_issueParams.setToken.getExternalPositionModules(components[i]).length == 0, "FlashMint: EXTERNAL POSITION MODULES NOT SUPPORTED");
uint256 wethSold = dexAdapter.swapTokensForExactTokens(componentUnits[i], type(uint256).max, _issueParams.componentSwapData[i]);
totalWethSpent = totalWethSpent.add(wethSold);
}
}
/**
* Calculates the amount of WETH required to buy all components required for issuance.
*
* @param _issueParams Struct containing addresses, amounts, and swap data for issuance
*
* @return totalWethCosts Amount of WETH needed to swap into component units required for issuance
*/
function _getWethCostsForIssue(IssueRedeemParams memory _issueParams)
internal
returns (uint256 totalWethCosts)
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredIssuanceComponents(
_issueParams.issuanceModule,
_issueParams.isDebtIssuance,
_issueParams.setToken,
_issueParams.amountSetToken
);
require(components.length == _issueParams.componentSwapData.length, "FlashMint: INVALID NUMBER OF COMPONENTS IN SWAP DATA");
totalWethCosts = 0;
for (uint256 i = 0; i < components.length; i++) {
if (components[i] == address(WETH)) {
totalWethCosts += componentUnits[i];
} else {
totalWethCosts += dexAdapter.getAmountIn(
_issueParams.componentSwapData[i],
componentUnits[i]
);
}
}
}
/**
* Transfers given amount of set token from the sender and redeems it for underlying components.
* Obtained component tokens are sent to this contract.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeem(ISetToken _setToken, uint256 _amount, address _issuanceModule) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
IBasicIssuanceModule(_issuanceModule).redeem(_setToken, _amount, address(this));
}
/**
* Sells redeemed components for WETH.
*
* @param _redeemParams Struct containing addresses, amounts, and swap data for issuance
*
* @return totalWethReceived Total amount of WETH received after liquidating all SetToken components
*/
function _sellComponentsForWeth(IssueRedeemParams memory _redeemParams)
internal
returns (uint256 totalWethReceived)
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredRedemptionComponents(
_redeemParams.issuanceModule,
_redeemParams.isDebtIssuance,
_redeemParams.setToken,
_redeemParams.amountSetToken
);
require(components.length == _redeemParams.componentSwapData.length, "FlashMint: INVALID NUMBER OF COMPONENTS IN SWAP DATA");
totalWethReceived = 0;
for (uint256 i = 0; i < components.length; i++) {
require(_redeemParams.setToken.getExternalPositionModules(components[i]).length == 0, "FlashMint: EXTERNAL POSITION MODULES NOT SUPPORTED");
uint256 wethBought = dexAdapter.swapExactTokensForTokens(componentUnits[i], 0, _redeemParams.componentSwapData[i]);
totalWethReceived = totalWethReceived.add(wethBought);
}
}
/**
* Calculates the amount of WETH received for selling off all components after redemption.
*
* @param _redeemParams Struct containing addresses, amounts, and swap data for redemption
*
* @return totalWethReceived Amount of WETH received after swapping all component tokens
*/
function _getWethReceivedForRedeem(IssueRedeemParams memory _redeemParams)
internal
returns (uint256 totalWethReceived)
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredRedemptionComponents(
_redeemParams.issuanceModule,
_redeemParams.isDebtIssuance,
_redeemParams.setToken,
_redeemParams.amountSetToken
);
require(components.length == _redeemParams.componentSwapData.length, "FlashMint: INVALID NUMBER OF COMPONENTS IN SWAP DATA");
totalWethReceived = 0;
for (uint256 i = 0; i < components.length; i++) {
if (components[i] == address(WETH)) {
totalWethReceived += componentUnits[i];
} else {
totalWethReceived += dexAdapter.getAmountOut(
_redeemParams.componentSwapData[i],
componentUnits[i]
);
}
}
}
/**
* Returns component positions required for issuance
*
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
* @param _setToken Set token to issue
* @param _amountSetToken Amount of set token to issue
*/
function getRequiredIssuanceComponents(address _issuanceModule, bool _isDebtIssuance, ISetToken _setToken, uint256 _amountSetToken) public view returns(address[] memory components, uint256[] memory positions) {
if(_isDebtIssuance) {
(components, positions, ) = IDebtIssuanceModule(_issuanceModule).getRequiredComponentIssuanceUnits(_setToken, _amountSetToken);
}
else {
(components, positions) = IBasicIssuanceModule(_issuanceModule).getRequiredComponentUnitsForIssue(_setToken, _amountSetToken);
}
}
/**
* Returns component positions required for Redemption
*
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
* @param _setToken Set token to issue
* @param _amountSetToken Amount of set token to issue
*/
function getRequiredRedemptionComponents(address _issuanceModule, bool _isDebtIssuance, ISetToken _setToken, uint256 _amountSetToken) public view returns(address[] memory components, uint256[] memory positions) {
if(_isDebtIssuance) {
(components, positions, ) = IDebtIssuanceModule(_issuanceModule).getRequiredComponentRedemptionUnits(_setToken, _amountSetToken);
}
else {
components = _setToken.getComponents();
positions = new uint256[](components.length);
for(uint256 i = 0; i < components.length; i++) {
uint256 unit = uint256(_setToken.getDefaultPositionRealUnit(components[i]));
positions[i] = unit.preciseMul(_amountSetToken);
}
}
}
}
FlashMintHyETH.sol 656 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IERC4626 } from "../interfaces/IERC4626.sol";
import { IStETH } from "../interfaces/external/IStETH.sol";
import { IAcrossHubPoolV2 } from "../interfaces/external/IAcrossHubPoolV2.sol";
import { IPendlePrincipalToken } from "../interfaces/external/IPendlePrincipalToken.sol";
import { IPendleMarketV3 } from "../interfaces/external/IPendleMarketV3.sol";
import { IPendleStandardizedYield } from "../interfaces/external/IPendleStandardizedYield.sol";
import { IController } from "../interfaces/IController.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { DEXAdapterV2 } from "./DEXAdapterV2.sol";
/**
* @title FlashMintHyETH
*/
contract FlashMintHyETH is Ownable, ReentrancyGuard {
using DEXAdapterV2 for DEXAdapterV2.Addresses;
using Address for address payable;
using Address for address;
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
struct PendleMarketData {
IPendlePrincipalToken pt;
IPendleStandardizedYield sy;
address underlying;
uint256 exchangeRateFactor;
}
/* ============ Constants ============= */
uint256 private constant MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR = 10;
IERC20 public constant acrossToken = IERC20(0x28F77208728B0A45cAb24c4868334581Fe86F95B);
IAcrossHubPoolV2 public constant acrossPool =
IAcrossHubPoolV2(0xc186fA914353c44b2E33eBE05f21846F1048bEda);
/* ============ Immutables ============ */
IController public immutable setController;
IStETH public immutable stETH;
IDebtIssuanceModule public immutable issuanceModule; // interface is compatible with DebtIssuanceModuleV2
mapping(IPendlePrincipalToken => IPendleMarketV3) public pendleMarkets;
mapping(IPendleMarketV3 => PendleMarketData) public pendleMarketData;
mapping(address => mapping(address => DEXAdapterV2.SwapData)) public swapData;
/* ============ State Variables ============ */
DEXAdapterV2.Addresses public dexAdapter;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the minted Set token
ISetToken indexed _setToken, // The minted Set token
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to mint the Set tokens
uint256 _amountInputToken, // The amount of input tokens used for minting
uint256 _amountSetIssued // The amount of Set tokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the Set token
ISetToken indexed _setToken, // The redeemed Set token
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of Set token redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
/**
* checks that _setToken is a valid listed set token on the setController
*
* @param _setToken set token to check
*/
modifier isSetToken(ISetToken _setToken) {
require(setController.isSet(address(_setToken)), "FlashMint: INVALID_SET");
_;
}
/**
* checks that _inputToken is the first adress in _path and _outputToken is the last address in _path
*
* @param _path Array of addresses for a DEX swap path
* @param _inputToken input token of DEX swap
* @param _outputToken output token of DEX swap
*/
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
) {
if (_inputToken != _outputToken) {
require(
_path[0] == _inputToken ||
(_inputToken == dexAdapter.weth && _path[0] == DEXAdapterV2.ETH_ADDRESS),
"FlashMint: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length - 1] == _outputToken ||
(_outputToken == dexAdapter.weth &&
_path[_path.length - 1] == DEXAdapterV2.ETH_ADDRESS),
"FlashMint: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ========== Constructor ========== */
constructor(
DEXAdapterV2.Addresses memory _dexAddresses,
IController _setController,
IDebtIssuanceModule _issuanceModule,
IStETH _stETH,
address _stEthETHPool
) public {
dexAdapter = _dexAddresses;
setController = _setController;
issuanceModule = _issuanceModule;
stETH = _stETH;
IERC20(address(_stETH)).approve(_stEthETHPool, MAX_UINT256);
}
/* ============ External Functions (Publicly Accesible) ============ */
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external isSetToken(_setToken) {
address[] memory _components = _setToken.getComponents();
for (uint256 i = 0; i < _components.length; ++i) {
IERC20(_components[i]).approve(address(issuanceModule), MAX_UINT256);
}
_setToken.approve(address(issuanceModule), MAX_UINT256);
}
/**
* Issue exact amout of SetToken from ETH
*
* @param _setToken Address of the SetToken to issue
* @param _amountSetToken Amount of SetToken to issue
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _amountSetToken,
DEXAdapterV2.SwapData[] memory _swapDataEthToComponent
) external payable nonReentrant returns (uint256) {
uint256 ethSpent = _issueExactSetFromEth(_setToken, _amountSetToken, _swapDataEthToComponent);
msg.sender.sendValue(msg.value.sub(ethSpent));
return ethSpent;
}
/**
* Issue exact amout of SetToken from ERC20 token
*
* @param _setToken Address of the SetToken to issue
* @param _amountSetToken Amount of SetToken to issue
* @param _inputToken Address of the input token
* @param _maxInputTokenAmount Maximum amount of input token to spend
* @param _swapDataInputTokenToEth Swap data from input token to ETH
* @param _swapDataEthToInputToken Swap data from ETH to input token (used to swap back the leftover eth)
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _amountSetToken,
IERC20 _inputToken,
uint256 _maxInputTokenAmount,
DEXAdapterV2.SwapData memory _swapDataInputTokenToEth,
DEXAdapterV2.SwapData memory _swapDataEthToInputToken,
DEXAdapterV2.SwapData[] memory _swapDataEthToComponent
) external payable nonReentrant returns (uint256) {
_inputToken.safeTransferFrom(msg.sender, address(this), _maxInputTokenAmount);
uint256 ethAmount = _swapFromTokenToEth(_inputToken, _maxInputTokenAmount, _swapDataInputTokenToEth);
ethAmount = ethAmount.sub(_issueExactSetFromEth(_setToken, _amountSetToken, _swapDataEthToComponent));
uint256 inputTokenLeft = _swapFromEthToToken(_inputToken, ethAmount, _swapDataEthToInputToken);
_inputToken.safeTransfer(msg.sender, inputTokenLeft);
return _maxInputTokenAmount.sub(inputTokenLeft);
}
/**
* Redeem exact amount of SetToken for ETH
*
* @param _setToken Address of the SetToken to redeem
* @param _amountSetToken Amount of SetToken to redeem
* @param _minETHOut Minimum amount of ETH to receive (tx will revert if actual amount is less)
* @param _swapDataComponentToEth Swap data from component to ETH (for non standard components)
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minETHOut,
DEXAdapterV2.SwapData[] memory _swapDataComponentToEth
) external payable nonReentrant returns (uint256) {
uint256 ethObtained = _redeemExactSetForETH(_setToken, _amountSetToken, _minETHOut, _swapDataComponentToEth);
require(ethObtained >= _minETHOut, "FlashMint: INSUFFICIENT_OUTPUT");
msg.sender.sendValue(ethObtained);
return ethObtained;
}
/**
* Redeem exact amount of SetToken for ERC20
*
* @param _setToken Address of the SetToken to redeem
* @param _amountSetToken Amount of SetToken to redeem
* @param _outputToken Address of the output token
* @param _minOutputTokenAmount Minimum amount of output token to receive (tx will revert if actual amount is less)
* @param _swapDataEthToOutputToken Swap data from ETH to output token
* @param _swapDataComponentToEth Swap data from component to ETH (for non standard components)
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _amountSetToken,
IERC20 _outputToken,
uint256 _minOutputTokenAmount,
DEXAdapterV2.SwapData memory _swapDataEthToOutputToken,
DEXAdapterV2.SwapData[] memory _swapDataComponentToEth
) external payable nonReentrant returns (uint256) {
uint256 ethObtained = _redeemExactSetForETH(_setToken, _amountSetToken, 0, _swapDataComponentToEth);
uint256 outputTokenAmount = _swapFromEthToToken(_outputToken, ethObtained, _swapDataEthToOutputToken);
require(outputTokenAmount >= _minOutputTokenAmount, "FlashMint: INSUFFICIENT_OUTPUT");
_outputToken.safeTransfer(msg.sender, outputTokenAmount);
return outputTokenAmount;
}
receive() external payable {}
/* ============ External Functions (Access controlled) ============ */
/**
* Approve spender to spend specific token on behalf of this contract
*
* @param _token Address of the token to approve
* @param _spender Address of the spender
* @param _allowance Amount to approve
*/
function approveToken(IERC20 _token, address _spender, uint256 _allowance) external onlyOwner {
_token.approve(_spender, _allowance);
}
/**
* Withdraw slippage to selected address
*
* @param _tokens Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(
IERC20[] calldata _tokens,
address payable _to
) external payable onlyOwner {
for (uint256 i = 0; i < _tokens.length; i++) {
if (address(_tokens[i]) == DEXAdapterV2.ETH_ADDRESS) {
_to.sendValue(address(this).balance);
} else {
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
/**
* Set swap data for specific token pair
*
* @param _inputToken Address of the input token
* @param _outputToken Address of the output token
* @param _swapData Swap data for the token pair describing DEX / route
*/
function setSwapData(
address _inputToken,
address _outputToken,
DEXAdapterV2.SwapData memory _swapData
) external onlyOwner {
swapData[_inputToken][_outputToken] = _swapData;
}
/**
* Set Pendle Market to use for specific pt including relevant metadata
*
* @param _pt Address of the Pendle Principal Token
* @param _sy Address of the corresponding Standardized Yield Token
* @param _underlying Address of the underlying token to redeem to
* @param _market Address of the Pendle Market to use for swapping between pt and sy
* @param _exchangeRateFactor Factor to multiply the exchange rate when supplying to Pendle Market
*/
function setPendleMarket(
IPendlePrincipalToken _pt,
IPendleStandardizedYield _sy,
address _underlying,
IPendleMarketV3 _market,
uint256 _exchangeRateFactor
) external onlyOwner {
pendleMarkets[_pt] = _market;
pendleMarketData[_market] = PendleMarketData({
pt: _pt,
sy: _sy,
underlying: _underlying,
exchangeRateFactor: _exchangeRateFactor
});
}
/**
* Callback method that is called by Pendle Market during the swap to request input token
*
* @param _ptToAccount Swap balance of pt token (negative -> swapping pt to sy)
* @param _syToAccount Swap balance of sy token (negative -> swapping sy to pt)
* @param _data Arbitrary data passed by Pendle Market (not used)
*/
function swapCallback(int256 _ptToAccount, int256 _syToAccount, bytes calldata _data) external {
PendleMarketData storage marketData = pendleMarketData[IPendleMarketV3(msg.sender)];
require(address(marketData.sy) != address(0), "ISC");
if (_ptToAccount < 0) {
uint256 ptAmount = uint256(-_ptToAccount);
marketData.pt.transfer(msg.sender, ptAmount);
} else if (_syToAccount < 0) {
uint256 syAmount = uint256(-_syToAccount);
// Withdraw necessary ETH, if deposit size is enough to move the oracle, then the exchange rate will not be
// valid for computing the amount of ETH to withdraw, so increase by exchangeRateFactor
uint256 ethAmount = syAmount.mul(marketData.sy.exchangeRate()).div(1 ether);
uint256 syAmountPreview = marketData.sy.previewDeposit(address(0), ethAmount);
if (syAmountPreview < syAmount) {
ethAmount = ethAmount * marketData.exchangeRateFactor / 1 ether;
}
marketData.sy.deposit{ value: ethAmount }(msg.sender, address(0), ethAmount, 0);
} else {
revert("Invalid callback");
}
}
/* ============ Internal ============ */
/**
* @dev Issue exact amount of SetToken from ETH
*
*/
function _issueExactSetFromEth(
ISetToken _setToken,
uint256 _amountSetToken,
DEXAdapterV2.SwapData[] memory _swapDataEthToComponent
) internal returns (uint256) {
(address[] memory components, uint256[] memory positions, ) = IDebtIssuanceModule(
issuanceModule
).getRequiredComponentIssuanceUnits(_setToken, _amountSetToken);
uint256 ethBalanceBefore = address(this).balance;
for (uint256 i = 0; i < components.length; i++) {
_depositIntoComponent(components[i], positions[i], _swapDataEthToComponent[i]);
}
issuanceModule.issue(_setToken, _amountSetToken, msg.sender);
return ethBalanceBefore.sub(address(this).balance);
}
/**
* @dev Redeem exact amount of SetToken for ETH
*
*/
function _redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minETHOut,
DEXAdapterV2.SwapData[] memory _swapDataComponentToEth
) internal returns (uint256) {
uint256 ethBalanceBefore = address(this).balance;
_setToken.safeTransferFrom(msg.sender, address(this), _amountSetToken);
issuanceModule.redeem(_setToken, _amountSetToken, address(this));
(address[] memory components, uint256[] memory positions, ) = IDebtIssuanceModule(
issuanceModule
).getRequiredComponentRedemptionUnits(_setToken, _amountSetToken);
for (uint256 i = 0; i < components.length; i++) {
_withdrawFromComponent(components[i], positions[i], _swapDataComponentToEth[i]);
}
return address(this).balance.sub(ethBalanceBefore);
}
/**
* @dev Deposit ETH into given component
*
*/
function _depositIntoComponent(
address _component,
uint256 _amount,
DEXAdapterV2.SwapData memory _swapData
) internal {
if(_swapData.exchange != DEXAdapterV2.Exchange.None) {
require(_swapData.path.length > 1, "zero length swap path");
require(_swapData.path[0] == DEXAdapterV2.ETH_ADDRESS || _swapData.path[0] == dexAdapter.weth, "Invalid input token");
require(_swapData.path[_swapData.path.length - 1] == _component, "Invalid output token");
if(_swapData.path[0] == dexAdapter.weth) {
uint256 balanceBefore = IWETH(dexAdapter.weth).balanceOf(address(this));
IWETH(dexAdapter.weth).deposit{value: address(this).balance}();
dexAdapter.swapTokensForExactTokens(_amount, IWETH(dexAdapter.weth).balanceOf(address(this)), _swapData);
IWETH(dexAdapter.weth).withdraw(IWETH(dexAdapter.weth).balanceOf(address(this)).sub(balanceBefore));
}
else {
dexAdapter.swapTokensForExactTokens(_amount, address(this).balance, _swapData);
}
return;
}
if (_isInstadapp(_component)) {
_depositIntoInstadapp(IERC4626(_component), _amount);
return;
}
IPendleStandardizedYield syToken = _getSyToken(IPendlePrincipalToken(_component));
if (syToken != IPendleStandardizedYield(address(0))) {
_depositIntoPendle(IPendlePrincipalToken(_component), _amount, syToken);
return;
}
if (IERC20(_component) == acrossToken) {
_depositIntoAcross(_amount);
return;
}
if (_component == dexAdapter.weth) {
IWETH(dexAdapter.weth).deposit{ value: _amount }();
return;
}
revert("Missing Swapdata for non-standard component");
}
/**
* @dev Withdraw ETH from given component
*
*/
function _withdrawFromComponent(
address _component,
uint256 _amount,
DEXAdapterV2.SwapData memory _swapData
) internal {
if(_swapData.exchange != DEXAdapterV2.Exchange.None) {
require(_swapData.path.length > 1, "zero length swap path");
require(_swapData.path[0] == _component, "Invalid input token");
require(_swapData.path[_swapData.path.length - 1] == DEXAdapterV2.ETH_ADDRESS || _swapData.path[_swapData.path.length - 1] == dexAdapter.weth, "Invalid output token");
uint256 ethReceived = dexAdapter.swapExactTokensForTokens(_amount, 0, _swapData);
if(_swapData.path[_swapData.path.length - 1] == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(ethReceived);
}
return;
}
if (_isInstadapp(_component)) {
_withdrawFromInstadapp(IERC4626(_component), _amount);
return;
}
IPendleMarketV3 market = pendleMarkets[IPendlePrincipalToken(_component)];
if (market != IPendleMarketV3(address(0))) {
_withdrawFromPendle(IPendlePrincipalToken(_component), _amount, market);
return;
}
if (IERC20(_component) == acrossToken) {
_withdrawFromAcross(_amount);
return;
}
if (_component == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(_amount);
return;
}
revert("Missing Swapdata for non-standard component");
}
/**
* @dev Deposit eth into steth and then into instadapp vault
*
*/
function _depositIntoInstadapp(IERC4626 _vault, uint256 _amount) internal {
uint256 stETHAmount = _vault.previewMint(_amount);
_depositIntoLido(stETHAmount);
_vault.mint(_amount, address(this));
}
/**
* @dev Deposit eth into steth
*
*/
function _depositIntoLido(uint256 _amount) internal {
stETH.submit{ value: _amount }(address(0));
}
/**
* @dev Withdraw steth from instadapp vault and then swap to eth
* @dev Requries the respective swap data (stETH -> ETH) to be set
*
*/
function _withdrawFromInstadapp(IERC4626 _vault, uint256 _amount) internal {
uint256 stETHAmount = _vault.redeem(_amount, address(this), address(this));
_swapExactTokensForTokens(stETHAmount, address(stETH), address(0));
}
/**
* @dev Check if given component is the Instadapp vault
*
*/
function _isInstadapp(address _token) internal pure returns (bool) {
return _token == 0xA0D3707c569ff8C87FA923d3823eC5D81c98Be78;
}
/**
* @dev Get Sy token for given pt token
* @dev Also functions as check if given component is a Pendle Principal Token
*
*/
function _getSyToken(
IPendlePrincipalToken _pt
) internal view returns (IPendleStandardizedYield) {
return pendleMarketData[pendleMarkets[_pt]].sy;
}
/**
* @dev Initiate deposit into pendle by swapping pt for sy
* @dev Deposit from eth to sy is done in swapCallback
*/
function _depositIntoPendle(
IPendlePrincipalToken _pt,
uint256 _ptAmount,
IPendleStandardizedYield _sy
) internal {
// Adding random bytes here since PendleMarket will not call back if data is empty
IPendleMarketV3(pendleMarkets[_pt]).swapSyForExactPt(address(this), _ptAmount, bytes("a"));
}
/**
* @dev Obtain across lp tokens by adding eth liquidity into the across pool
*/
function _depositIntoAcross(uint256 _acrossLpAmount) internal {
uint256 ethAmount = acrossPool
.exchangeRateCurrent(dexAdapter.weth)
.mul(_acrossLpAmount)
.div(1e18)
.add(ROUNDING_ERROR);
acrossPool.addLiquidity{ value: ethAmount }(dexAdapter.weth, ethAmount);
}
/**
* @dev Withdraw eth by removing liquidity from across pool
*/
function _withdrawFromAcross(uint256 _acrossLpAmount) internal {
acrossPool.removeLiquidity(dexAdapter.weth, _acrossLpAmount, true);
}
/**
* @dev Withdraw from Pendle by swapping pt for sy, redeeming sy for underlying and swapping underlying to eth
*/
function _withdrawFromPendle(
IPendlePrincipalToken _pt,
uint256 _ptAmount,
IPendleMarketV3 _pendleMarket
) internal {
// Adding random bytes here since PendleMarket will not call back if data is empty
(uint256 syAmount, ) = _pendleMarket.swapExactPtForSy(address(this), _ptAmount, bytes("a"));
PendleMarketData storage data = pendleMarketData[_pendleMarket];
uint256 amountUnderlying = data.sy.redeem(
address(this),
syAmount,
data.underlying,
0,
false
);
_swapExactTokensForTokens(amountUnderlying, data.underlying, address(0));
IWETH(dexAdapter.weth).withdraw(IERC20(dexAdapter.weth).balanceOf(address(this)));
}
/**
* @dev Swap exact amount of input token for output token using configured swap data
*/
function _swapExactTokensForTokens(
uint256 _amountIn,
address _inputToken,
address _outputToken
) internal returns (uint256) {
dexAdapter.swapExactTokensForTokens(_amountIn, 0, swapData[_inputToken][_outputToken]);
}
/**
* @dev Convert ETH to specified token, either swapping or simply depositing if outputToken is WETH
*/
function _swapFromEthToToken(
IERC20 _outputToken,
uint256 _ethAmount,
DEXAdapterV2.SwapData memory _swapDataEthToOutputToken
) internal returns(uint256 outputTokenAmount) {
if(address(_outputToken) == address(dexAdapter.weth)) {
outputTokenAmount = _ethAmount;
IWETH(dexAdapter.weth).deposit{value: _ethAmount}();
} else {
if(_swapDataEthToOutputToken.path[0] == dexAdapter.weth) {
IWETH(dexAdapter.weth).deposit{value: _ethAmount}();
}
outputTokenAmount = dexAdapter.swapExactTokensForTokens(
_ethAmount,
0,
_swapDataEthToOutputToken
);
}
}
/**
* @dev Convert specified token to ETH, either swapping or simply withdrawing if inputToken is WETH
*/
function _swapFromTokenToEth(
IERC20 _inputToken,
uint256 _maxInputTokenAmount,
DEXAdapterV2.SwapData memory _swapDataInputTokenToEth
) internal returns (uint256 ethAmount) {
if(address(_inputToken) == dexAdapter.weth) {
ethAmount = _maxInputTokenAmount;
IWETH(dexAdapter.weth).withdraw(ethAmount);
} else {
ethAmount = dexAdapter.swapExactTokensForTokens(
_maxInputTokenAmount,
0,
_swapDataInputTokenToEth
);
if(_swapDataInputTokenToEth.path[_swapDataInputTokenToEth.path.length - 1] == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(ethAmount);
}
}
}
}
FlashMintHyETHV2.sol 693 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IERC4626 } from "../interfaces/IERC4626.sol";
import { IStETH } from "../interfaces/external/IStETH.sol";
import { IAcrossHubPoolV2 } from "../interfaces/external/IAcrossHubPoolV2.sol";
import { IPendlePrincipalToken } from "../interfaces/external/IPendlePrincipalToken.sol";
import { IPendleMarketV3 } from "../interfaces/external/IPendleMarketV3.sol";
import { IPendleStandardizedYield } from "../interfaces/external/IPendleStandardizedYield.sol";
import { IController } from "../interfaces/IController.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { DEXAdapterV2 } from "./DEXAdapterV2.sol";
/**
* @title FlashMintHyETHV2
*/
contract FlashMintHyETHV2 is Ownable, ReentrancyGuard {
using DEXAdapterV2 for DEXAdapterV2.Addresses;
using Address for address payable;
using Address for address;
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
struct PendleMarketData {
IPendlePrincipalToken pt;
IPendleStandardizedYield sy;
address underlying;
uint256 exchangeRateFactor;
}
/* ============ Constants ============= */
uint256 private constant MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR = 10;
IERC20 public constant acrossToken = IERC20(0x28F77208728B0A45cAb24c4868334581Fe86F95B);
IAcrossHubPoolV2 public constant acrossPool =
IAcrossHubPoolV2(0xc186fA914353c44b2E33eBE05f21846F1048bEda);
/* ============ Immutables ============ */
IController public immutable setController;
IStETH public immutable stETH;
IDebtIssuanceModule public immutable issuanceModule; // interface is compatible with DebtIssuanceModuleV2
mapping(IPendlePrincipalToken => IPendleMarketV3) public pendleMarkets;
mapping(IPendleMarketV3 => PendleMarketData) public pendleMarketData;
mapping(address => mapping(address => DEXAdapterV2.SwapData)) public swapData;
mapping(address => bool) public erc4626Components;
/* ============ State Variables ============ */
DEXAdapterV2.Addresses public dexAdapter;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the minted Set token
ISetToken indexed _setToken, // The minted Set token
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to mint the Set tokens
uint256 _amountInputToken, // The amount of input tokens used for minting
uint256 _amountSetIssued // The amount of Set tokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the Set token
ISetToken indexed _setToken, // The redeemed Set token
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of Set token redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
/**
* checks that _setToken is a valid listed set token on the setController
*
* @param _setToken set token to check
*/
modifier isSetToken(ISetToken _setToken) {
require(setController.isSet(address(_setToken)), "FlashMint: INVALID_SET");
_;
}
/**
* checks that _inputToken is the first adress in _path and _outputToken is the last address in _path
*
* @param _path Array of addresses for a DEX swap path
* @param _inputToken input token of DEX swap
* @param _outputToken output token of DEX swap
*/
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
) {
if (_inputToken != _outputToken) {
require(
_path[0] == _inputToken ||
(_inputToken == dexAdapter.weth && _path[0] == DEXAdapterV2.ETH_ADDRESS),
"FlashMint: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length - 1] == _outputToken ||
(_outputToken == dexAdapter.weth &&
_path[_path.length - 1] == DEXAdapterV2.ETH_ADDRESS),
"FlashMint: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ========== Constructor ========== */
constructor(
DEXAdapterV2.Addresses memory _dexAddresses,
IController _setController,
IDebtIssuanceModule _issuanceModule,
IStETH _stETH,
address _stEthETHPool
) public {
dexAdapter = _dexAddresses;
setController = _setController;
issuanceModule = _issuanceModule;
stETH = _stETH;
IERC20(address(_stETH)).approve(_stEthETHPool, MAX_UINT256);
}
/* ============ External Functions (Publicly Accesible) ============ */
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external isSetToken(_setToken) {
address[] memory _components = _setToken.getComponents();
for (uint256 i = 0; i < _components.length; ++i) {
IERC20(_components[i]).approve(address(issuanceModule), MAX_UINT256);
}
_setToken.approve(address(issuanceModule), MAX_UINT256);
}
/**
* Issue exact amout of SetToken from ETH
*
* @param _setToken Address of the SetToken to issue
* @param _amountSetToken Amount of SetToken to issue
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _amountSetToken,
DEXAdapterV2.SwapData[] memory _swapDataEthToComponent
) external payable nonReentrant returns (uint256) {
uint256 ethSpent = _issueExactSetFromEth(_setToken, _amountSetToken, _swapDataEthToComponent);
msg.sender.sendValue(msg.value.sub(ethSpent));
return ethSpent;
}
/**
* Issue exact amout of SetToken from ERC20 token
*
* @param _setToken Address of the SetToken to issue
* @param _amountSetToken Amount of SetToken to issue
* @param _inputToken Address of the input token
* @param _maxInputTokenAmount Maximum amount of input token to spend
* @param _swapDataInputTokenToEth Swap data from input token to ETH
* @param _swapDataEthToInputToken Swap data from ETH to input token (used to swap back the leftover eth)
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _amountSetToken,
IERC20 _inputToken,
uint256 _maxInputTokenAmount,
DEXAdapterV2.SwapData memory _swapDataInputTokenToEth,
DEXAdapterV2.SwapData memory _swapDataEthToInputToken,
DEXAdapterV2.SwapData[] memory _swapDataEthToComponent
) external payable nonReentrant returns (uint256) {
_inputToken.safeTransferFrom(msg.sender, address(this), _maxInputTokenAmount);
uint256 ethAmount = _swapExactTokenForEth(_inputToken, _maxInputTokenAmount, _swapDataInputTokenToEth);
ethAmount = ethAmount.sub(_issueExactSetFromEth(_setToken, _amountSetToken, _swapDataEthToComponent));
uint256 inputTokenLeft = _swapFromEthToToken(_inputToken, ethAmount, _swapDataEthToInputToken);
_inputToken.safeTransfer(msg.sender, inputTokenLeft);
return _maxInputTokenAmount.sub(inputTokenLeft);
}
/**
* Redeem exact amount of SetToken for ETH
*
* @param _setToken Address of the SetToken to redeem
* @param _amountSetToken Amount of SetToken to redeem
* @param _minETHOut Minimum amount of ETH to receive (tx will revert if actual amount is less)
* @param _swapDataComponentToEth Swap data from component to ETH (for non standard components)
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minETHOut,
DEXAdapterV2.SwapData[] memory _swapDataComponentToEth
) external payable nonReentrant returns (uint256) {
uint256 ethObtained = _redeemExactSetForETH(_setToken, _amountSetToken, _minETHOut, _swapDataComponentToEth);
require(ethObtained >= _minETHOut, "FlashMint: INSUFFICIENT_OUTPUT");
msg.sender.sendValue(ethObtained);
return ethObtained;
}
/**
* Redeem exact amount of SetToken for ERC20
*
* @param _setToken Address of the SetToken to redeem
* @param _amountSetToken Amount of SetToken to redeem
* @param _outputToken Address of the output token
* @param _minOutputTokenAmount Minimum amount of output token to receive (tx will revert if actual amount is less)
* @param _swapDataEthToOutputToken Swap data from ETH to output token
* @param _swapDataComponentToEth Swap data from component to ETH (for non standard components)
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _amountSetToken,
IERC20 _outputToken,
uint256 _minOutputTokenAmount,
DEXAdapterV2.SwapData memory _swapDataEthToOutputToken,
DEXAdapterV2.SwapData[] memory _swapDataComponentToEth
) external payable nonReentrant returns (uint256) {
uint256 ethObtained = _redeemExactSetForETH(_setToken, _amountSetToken, 0, _swapDataComponentToEth);
uint256 outputTokenAmount = _swapFromEthToToken(_outputToken, ethObtained, _swapDataEthToOutputToken);
require(outputTokenAmount >= _minOutputTokenAmount, "FlashMint: INSUFFICIENT_OUTPUT");
_outputToken.safeTransfer(msg.sender, outputTokenAmount);
return outputTokenAmount;
}
receive() external payable {}
/* ============ External Functions (Access controlled) ============ */
/**
* Control wether a component is registered as an ERC4626 token
*
* @param _component Address of the component
* @param _isERC4626 Boolean indicating if the component is an ERC4626 token
*/
function setERC4626Component(
address _component,
bool _isERC4626
) external onlyOwner {
erc4626Components[_component] = _isERC4626;
}
/**
* Approve spender to spend specific token on behalf of this contract
*
* @param _token Address of the token to approve
* @param _spender Address of the spender
* @param _allowance Amount to approve
*/
function approveToken(IERC20 _token, address _spender, uint256 _allowance) external onlyOwner {
_token.approve(_spender, _allowance);
}
/**
* Withdraw slippage to selected address
*
* @param _tokens Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(
IERC20[] calldata _tokens,
address payable _to
) external payable onlyOwner {
for (uint256 i = 0; i < _tokens.length; i++) {
if (address(_tokens[i]) == DEXAdapterV2.ETH_ADDRESS) {
_to.sendValue(address(this).balance);
} else {
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
/**
* Set swap data for specific token pair
*
* @param _inputToken Address of the input token
* @param _outputToken Address of the output token
* @param _swapData Swap data for the token pair describing DEX / route
*/
function setSwapData(
address _inputToken,
address _outputToken,
DEXAdapterV2.SwapData memory _swapData
) external onlyOwner {
swapData[_inputToken][_outputToken] = _swapData;
}
/**
* Set Pendle Market to use for specific pt including relevant metadata
*
* @param _pt Address of the Pendle Principal Token
* @param _sy Address of the corresponding Standardized Yield Token
* @param _underlying Address of the underlying token to redeem to
* @param _market Address of the Pendle Market to use for swapping between pt and sy
* @param _exchangeRateFactor Factor to multiply the exchange rate when supplying to Pendle Market
*/
function setPendleMarket(
IPendlePrincipalToken _pt,
IPendleStandardizedYield _sy,
address _underlying,
IPendleMarketV3 _market,
uint256 _exchangeRateFactor
) external onlyOwner {
pendleMarkets[_pt] = _market;
pendleMarketData[_market] = PendleMarketData({
pt: _pt,
sy: _sy,
underlying: _underlying,
exchangeRateFactor: _exchangeRateFactor
});
}
/**
* Callback method that is called by Pendle Market during the swap to request input token
*
* @param _ptToAccount Swap balance of pt token (negative -> swapping pt to sy)
* @param _syToAccount Swap balance of sy token (negative -> swapping sy to pt)
* @param _data Arbitrary data passed by Pendle Market (not used)
*/
function swapCallback(int256 _ptToAccount, int256 _syToAccount, bytes calldata _data) external {
PendleMarketData storage marketData = pendleMarketData[IPendleMarketV3(msg.sender)];
require(address(marketData.sy) != address(0), "ISC");
if (_ptToAccount < 0) {
uint256 ptAmount = uint256(-_ptToAccount);
marketData.pt.transfer(msg.sender, ptAmount);
} else if (_syToAccount < 0) {
uint256 syAmount = uint256(-_syToAccount);
// Withdraw necessary ETH, if deposit size is enough to move the oracle, then the exchange rate will not be
// valid for computing the amount of ETH to withdraw, so increase by exchangeRateFactor
uint256 ethAmount = syAmount.mul(marketData.sy.exchangeRate()).div(1 ether);
uint256 syAmountPreview = marketData.sy.previewDeposit(address(0), ethAmount);
if (syAmountPreview < syAmount) {
ethAmount = ethAmount * marketData.exchangeRateFactor / 1 ether;
}
marketData.sy.deposit{ value: ethAmount }(msg.sender, address(0), ethAmount, 0);
} else {
revert("Invalid callback");
}
}
/* ============ Internal ============ */
/**
* @dev Issue exact amount of SetToken from ETH
*
*/
function _issueExactSetFromEth(
ISetToken _setToken,
uint256 _amountSetToken,
DEXAdapterV2.SwapData[] memory _swapDataEthToComponent
) internal returns (uint256) {
(address[] memory components, uint256[] memory positions, ) = IDebtIssuanceModule(
issuanceModule
).getRequiredComponentIssuanceUnits(_setToken, _amountSetToken);
uint256 ethBalanceBefore = address(this).balance;
for (uint256 i = 0; i < components.length; i++) {
_depositIntoComponent(components[i], positions[i], _swapDataEthToComponent[i]);
}
issuanceModule.issue(_setToken, _amountSetToken, msg.sender);
return ethBalanceBefore.sub(address(this).balance);
}
/**
* @dev Redeem exact amount of SetToken for ETH
*
*/
function _redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minETHOut,
DEXAdapterV2.SwapData[] memory _swapDataComponentToEth
) internal returns (uint256) {
uint256 ethBalanceBefore = address(this).balance;
_setToken.safeTransferFrom(msg.sender, address(this), _amountSetToken);
issuanceModule.redeem(_setToken, _amountSetToken, address(this));
(address[] memory components, uint256[] memory positions, ) = IDebtIssuanceModule(
issuanceModule
).getRequiredComponentRedemptionUnits(_setToken, _amountSetToken);
for (uint256 i = 0; i < components.length; i++) {
_withdrawFromComponent(components[i], positions[i], _swapDataComponentToEth[i]);
}
return address(this).balance.sub(ethBalanceBefore);
}
/**
* @dev Deposit ETH into given component
*
*/
function _depositIntoComponent(
address _component,
uint256 _amount,
DEXAdapterV2.SwapData memory _swapData
) internal {
if(_swapData.exchange != DEXAdapterV2.Exchange.None) {
_swapEthForExactToken(_component, _amount, _swapData);
return;
}
if (_isInstadapp(_component)) {
_depositIntoInstadapp(IERC4626(_component), _amount);
return;
}
IPendleStandardizedYield syToken = _getSyToken(IPendlePrincipalToken(_component));
if (syToken != IPendleStandardizedYield(address(0))) {
_depositIntoPendle(IPendlePrincipalToken(_component), _amount, syToken);
return;
}
if (IERC20(_component) == acrossToken) {
_depositIntoAcross(_amount);
return;
}
if (_component == dexAdapter.weth) {
IWETH(dexAdapter.weth).deposit{ value: _amount }();
return;
}
if (erc4626Components[_component]) {
uint256 assetAmount = IERC4626(_component).previewMint(_amount);
address asset = IERC4626(_component).asset();
_swapEthForExactToken(asset, assetAmount, swapData[DEXAdapterV2.ETH_ADDRESS][asset]);
IERC4626(_component).mint(_amount, address(this));
return;
}
revert("Missing Swapdata for non-standard component");
}
/**
* @dev Withdraw ETH from given component
*
*/
function _withdrawFromComponent(
address _component,
uint256 _amount,
DEXAdapterV2.SwapData memory _swapData
) internal {
if(_swapData.exchange != DEXAdapterV2.Exchange.None) {
require(_swapData.path.length > 1, "zero length swap path");
require(_swapData.path[0] == _component, "Invalid input token");
require(_swapData.path[_swapData.path.length - 1] == DEXAdapterV2.ETH_ADDRESS || _swapData.path[_swapData.path.length - 1] == dexAdapter.weth, "Invalid output token");
uint256 ethReceived = dexAdapter.swapExactTokensForTokens(_amount, 0, _swapData);
if(_swapData.path[_swapData.path.length - 1] == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(ethReceived);
}
return;
}
if (_isInstadapp(_component)) {
_withdrawFromInstadapp(IERC4626(_component), _amount);
return;
}
IPendleMarketV3 market = pendleMarkets[IPendlePrincipalToken(_component)];
if (market != IPendleMarketV3(address(0))) {
_withdrawFromPendle(IPendlePrincipalToken(_component), _amount, market);
return;
}
if (IERC20(_component) == acrossToken) {
_withdrawFromAcross(_amount);
return;
}
if (_component == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(_amount);
return;
}
if (erc4626Components[_component]) {
address asset = IERC4626(_component).asset();
uint256 assetAmount = IERC4626(_component).redeem(_amount, address(this), address(this));
_swapExactTokenForEth(IERC20(asset), assetAmount, swapData[asset][DEXAdapterV2.ETH_ADDRESS]);
return;
}
revert("Missing Swapdata for non-standard component");
}
/**
* @dev Deposit eth into steth and then into instadapp vault
*
*/
function _depositIntoInstadapp(IERC4626 _vault, uint256 _amount) internal {
uint256 stETHAmount = _vault.previewMint(_amount);
_depositIntoLido(stETHAmount);
_vault.mint(_amount, address(this));
}
/**
* @dev Deposit eth into steth
*
*/
function _depositIntoLido(uint256 _amount) internal {
stETH.submit{ value: _amount }(address(0));
}
/**
* @dev Withdraw steth from instadapp vault and then swap to eth
* @dev Requries the respective swap data (stETH -> ETH) to be set
*
*/
function _withdrawFromInstadapp(IERC4626 _vault, uint256 _amount) internal {
uint256 stETHAmount = _vault.redeem(_amount, address(this), address(this));
_swapExactTokensForTokens(stETHAmount, address(stETH), address(0));
}
/**
* @dev Check if given component is the Instadapp vault
*
*/
function _isInstadapp(address _token) internal pure returns (bool) {
return _token == 0xA0D3707c569ff8C87FA923d3823eC5D81c98Be78;
}
/**
* @dev Get Sy token for given pt token
* @dev Also functions as check if given component is a Pendle Principal Token
*
*/
function _getSyToken(
IPendlePrincipalToken _pt
) internal view returns (IPendleStandardizedYield) {
return pendleMarketData[pendleMarkets[_pt]].sy;
}
/**
* @dev Initiate deposit into pendle by swapping pt for sy
* @dev Deposit from eth to sy is done in swapCallback
*/
function _depositIntoPendle(
IPendlePrincipalToken _pt,
uint256 _ptAmount,
IPendleStandardizedYield _sy
) internal {
// Adding random bytes here since PendleMarket will not call back if data is empty
IPendleMarketV3(pendleMarkets[_pt]).swapSyForExactPt(address(this), _ptAmount, bytes("a"));
}
/**
* @dev Obtain across lp tokens by adding eth liquidity into the across pool
*/
function _depositIntoAcross(uint256 _acrossLpAmount) internal {
uint256 ethAmount = acrossPool
.exchangeRateCurrent(dexAdapter.weth)
.mul(_acrossLpAmount)
.div(1e18)
.add(ROUNDING_ERROR);
acrossPool.addLiquidity{ value: ethAmount }(dexAdapter.weth, ethAmount);
}
/**
* @dev Withdraw eth by removing liquidity from across pool
*/
function _withdrawFromAcross(uint256 _acrossLpAmount) internal {
acrossPool.removeLiquidity(dexAdapter.weth, _acrossLpAmount, true);
}
/**
* @dev Withdraw from Pendle by swapping pt for sy, redeeming sy for underlying and swapping underlying to eth
*/
function _withdrawFromPendle(
IPendlePrincipalToken _pt,
uint256 _ptAmount,
IPendleMarketV3 _pendleMarket
) internal {
// Adding random bytes here since PendleMarket will not call back if data is empty
(uint256 syAmount, ) = _pendleMarket.swapExactPtForSy(address(this), _ptAmount, bytes("a"));
PendleMarketData storage data = pendleMarketData[_pendleMarket];
uint256 amountUnderlying = data.sy.redeem(
address(this),
syAmount,
data.underlying,
0,
false
);
_swapExactTokensForTokens(amountUnderlying, data.underlying, address(0));
IWETH(dexAdapter.weth).withdraw(IERC20(dexAdapter.weth).balanceOf(address(this)));
}
/**
* @dev Swap exact amount of input token for output token using configured swap data
*/
function _swapExactTokensForTokens(
uint256 _amountIn,
address _inputToken,
address _outputToken
) internal returns (uint256) {
dexAdapter.swapExactTokensForTokens(_amountIn, 0, swapData[_inputToken][_outputToken]);
}
/**
* @dev Convert ETH to specified token, either swapping or simply depositing if outputToken is WETH
*/
function _swapFromEthToToken(
IERC20 _outputToken,
uint256 _ethAmount,
DEXAdapterV2.SwapData memory _swapDataEthToOutputToken
) internal returns(uint256 outputTokenAmount) {
if(address(_outputToken) == address(dexAdapter.weth)) {
outputTokenAmount = _ethAmount;
IWETH(dexAdapter.weth).deposit{value: _ethAmount}();
} else {
if(_swapDataEthToOutputToken.path[0] == dexAdapter.weth) {
IWETH(dexAdapter.weth).deposit{value: _ethAmount}();
}
outputTokenAmount = dexAdapter.swapExactTokensForTokens(
_ethAmount,
0,
_swapDataEthToOutputToken
);
}
}
/**
* @dev Convert specified token to ETH, either swapping or simply withdrawing if inputToken is WETH
*/
function _swapExactTokenForEth(
IERC20 _inputToken,
uint256 _inputTokenAmount,
DEXAdapterV2.SwapData memory _swapDataInputTokenToEth
) internal returns (uint256 ethAmount) {
if(address(_inputToken) == dexAdapter.weth) {
ethAmount = _inputTokenAmount;
IWETH(dexAdapter.weth).withdraw(ethAmount);
} else {
ethAmount = dexAdapter.swapExactTokensForTokens(
_inputTokenAmount,
0,
_swapDataInputTokenToEth
);
if(_swapDataInputTokenToEth.path[_swapDataInputTokenToEth.path.length - 1] == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(ethAmount);
}
}
}
function _swapEthForExactToken(address _token, uint256 _amount, DEXAdapterV2.SwapData memory _swapData) internal {
if(_token == dexAdapter.weth) {
IWETH(dexAdapter.weth).deposit{value: _amount}();
return;
}
require(_swapData.path.length > 1, "zero length swap path");
require(_swapData.path[0] == DEXAdapterV2.ETH_ADDRESS || _swapData.path[0] == dexAdapter.weth, "Invalid input token");
require(_swapData.path[_swapData.path.length - 1] == _token, "Invalid output token");
if(_swapData.path[0] == dexAdapter.weth) {
uint256 balanceBefore = IWETH(dexAdapter.weth).balanceOf(address(this));
IWETH(dexAdapter.weth).deposit{value: address(this).balance}();
dexAdapter.swapTokensForExactTokens(_amount, IWETH(dexAdapter.weth).balanceOf(address(this)), _swapData);
IWETH(dexAdapter.weth).withdraw(IWETH(dexAdapter.weth).balanceOf(address(this)).sub(balanceBefore));
}
else {
dexAdapter.swapTokensForExactTokens(_amount, address(this).balance, _swapData);
}
}
}
FlashMintHyETHV3.sol 698 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IERC4626 } from "../interfaces/IERC4626.sol";
import { IStETH } from "../interfaces/external/IStETH.sol";
import { IAcrossHubPoolV2 } from "../interfaces/external/IAcrossHubPoolV2.sol";
import { IPendlePrincipalToken } from "../interfaces/external/IPendlePrincipalToken.sol";
import { IPendleMarketV3 } from "../interfaces/external/IPendleMarketV3.sol";
import { IPendleStandardizedYield } from "../interfaces/external/IPendleStandardizedYield.sol";
import { IRsEthAdapter } from "../interfaces/external/IRsEthAdapter.sol";
import { IController } from "../interfaces/IController.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { DEXAdapterV3 } from "./DEXAdapterV3.sol";
/**
* @title FlashMintHyETHV3
*/
contract FlashMintHyETHV3 is Ownable, ReentrancyGuard {
using DEXAdapterV3 for DEXAdapterV3.Addresses;
using Address for address payable;
using Address for address;
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
struct PendleMarketData {
IPendlePrincipalToken pt;
IPendleStandardizedYield sy;
address underlying;
uint256 exchangeRateFactor;
}
/* ============ Constants ============= */
uint256 private constant MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR = 10;
IERC20 public constant acrossToken = IERC20(0x28F77208728B0A45cAb24c4868334581Fe86F95B);
IAcrossHubPoolV2 public constant acrossPool =
IAcrossHubPoolV2(0xc186fA914353c44b2E33eBE05f21846F1048bEda);
IERC20 public constant agETH = IERC20(0xe1B4d34E8754600962Cd944B535180Bd758E6c2e);
IRsEthAdapter public constant rsEthAdapter = IRsEthAdapter(0xbf28C9FCb12A97441488f9C68FaA49811a98688a);
/* ============ Immutables ============ */
IController public immutable setController;
IStETH public immutable stETH;
IDebtIssuanceModule public immutable issuanceModule; // interface is compatible with DebtIssuanceModuleV2
mapping(IPendlePrincipalToken => IPendleMarketV3) public pendleMarkets;
mapping(IPendleMarketV3 => PendleMarketData) public pendleMarketData;
mapping(address => mapping(address => DEXAdapterV3.SwapData)) public swapData;
mapping(address => bool) public erc4626Components;
/* ============ State Variables ============ */
DEXAdapterV3.Addresses public dexAdapter;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the minted Set token
ISetToken indexed _setToken, // The minted Set token
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to mint the Set tokens
uint256 _amountInputToken, // The amount of input tokens used for minting
uint256 _amountSetIssued // The amount of Set tokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the Set token
ISetToken indexed _setToken, // The redeemed Set token
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of Set token redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
/**
* checks that _setToken is a valid listed set token on the setController
*
* @param _setToken set token to check
*/
modifier isSetToken(ISetToken _setToken) {
require(setController.isSet(address(_setToken)), "FlashMint: INVALID_SET");
_;
}
/**
* checks that _inputToken is the first adress in _path and _outputToken is the last address in _path
*
* @param _path Array of addresses for a DEX swap path
* @param _inputToken input token of DEX swap
* @param _outputToken output token of DEX swap
*/
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
) {
if (_inputToken != _outputToken) {
require(
_path[0] == _inputToken ||
(_inputToken == dexAdapter.weth && _path[0] == DEXAdapterV3.ETH_ADDRESS),
"FlashMint: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length - 1] == _outputToken ||
(_outputToken == dexAdapter.weth &&
_path[_path.length - 1] == DEXAdapterV3.ETH_ADDRESS),
"FlashMint: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ========== Constructor ========== */
constructor(
DEXAdapterV3.Addresses memory _dexAddresses,
IController _setController,
IDebtIssuanceModule _issuanceModule,
IStETH _stETH,
address _stEthETHPool
) public {
dexAdapter = _dexAddresses;
setController = _setController;
issuanceModule = _issuanceModule;
stETH = _stETH;
IERC20(address(_stETH)).approve(_stEthETHPool, MAX_UINT256);
}
/* ============ External Functions (Publicly Accesible) ============ */
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function needs to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external isSetToken(_setToken) {
address[] memory _components = _setToken.getComponents();
for (uint256 i = 0; i < _components.length; ++i) {
IERC20(_components[i]).approve(address(issuanceModule), MAX_UINT256);
}
_setToken.approve(address(issuanceModule), MAX_UINT256);
}
/**
* Issue exact amount of SetToken from ETH
*
* @param _setToken Address of the SetToken to issue
* @param _amountSetToken Amount of SetToken to issue
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _amountSetToken,
DEXAdapterV3.SwapData[] memory _swapDataEthToComponent
) external payable nonReentrant returns (uint256) {
uint256 ethSpent = _issueExactSetFromEth(_setToken, _amountSetToken, _swapDataEthToComponent);
msg.sender.sendValue(msg.value.sub(ethSpent));
return ethSpent;
}
/**
* Issue exact amount of SetToken from ERC20 token
*
* @param _setToken Address of the SetToken to issue
* @param _amountSetToken Amount of SetToken to issue
* @param _inputToken Address of the input token
* @param _maxInputTokenAmount Maximum amount of input token to spend
* @param _swapDataInputTokenToEth Swap data from input token to ETH
* @param _swapDataEthToInputToken Swap data from ETH to input token (used to swap back the leftover eth)
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _amountSetToken,
IERC20 _inputToken,
uint256 _maxInputTokenAmount,
DEXAdapterV3.SwapData memory _swapDataInputTokenToEth,
DEXAdapterV3.SwapData memory _swapDataEthToInputToken,
DEXAdapterV3.SwapData[] memory _swapDataEthToComponent
) external payable nonReentrant returns (uint256) {
_inputToken.safeTransferFrom(msg.sender, address(this), _maxInputTokenAmount);
uint256 ethAmount = _swapExactTokenForEth(_inputToken, _maxInputTokenAmount, _swapDataInputTokenToEth);
ethAmount = ethAmount.sub(_issueExactSetFromEth(_setToken, _amountSetToken, _swapDataEthToComponent));
uint256 inputTokenLeft = _swapFromEthToToken(_inputToken, ethAmount, _swapDataEthToInputToken);
_inputToken.safeTransfer(msg.sender, inputTokenLeft);
return _maxInputTokenAmount.sub(inputTokenLeft);
}
/**
* Redeem exact amount of SetToken for ETH
*
* @param _setToken Address of the SetToken to redeem
* @param _amountSetToken Amount of SetToken to redeem
* @param _minETHOut Minimum amount of ETH to receive (tx will revert if actual amount is less)
* @param _swapDataComponentToEth Swap data from component to ETH (for non-standard components)
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minETHOut,
DEXAdapterV3.SwapData[] memory _swapDataComponentToEth
) external payable nonReentrant returns (uint256) {
uint256 ethObtained = _redeemExactSetForETH(_setToken, _amountSetToken, _minETHOut, _swapDataComponentToEth);
require(ethObtained >= _minETHOut, "FlashMint: INSUFFICIENT_OUTPUT");
msg.sender.sendValue(ethObtained);
return ethObtained;
}
/**
* Redeem exact amount of SetToken for ERC20
*
* @param _setToken Address of the SetToken to redeem
* @param _amountSetToken Amount of SetToken to redeem
* @param _outputToken Address of the output token
* @param _minOutputTokenAmount Minimum amount of output token to receive (tx will revert if actual amount is less)
* @param _swapDataEthToOutputToken Swap data from ETH to output token
* @param _swapDataComponentToEth Swap data from component to ETH (for non-standard components)
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _amountSetToken,
IERC20 _outputToken,
uint256 _minOutputTokenAmount,
DEXAdapterV3.SwapData memory _swapDataEthToOutputToken,
DEXAdapterV3.SwapData[] memory _swapDataComponentToEth
) external payable nonReentrant returns (uint256) {
uint256 ethObtained = _redeemExactSetForETH(_setToken, _amountSetToken, 0, _swapDataComponentToEth);
uint256 outputTokenAmount = _swapFromEthToToken(_outputToken, ethObtained, _swapDataEthToOutputToken);
require(outputTokenAmount >= _minOutputTokenAmount, "FlashMint: INSUFFICIENT_OUTPUT");
_outputToken.safeTransfer(msg.sender, outputTokenAmount);
return outputTokenAmount;
}
receive() external payable {}
/* ============ External Functions (Access controlled) ============ */
/**
* Control wether a component is registered as an ERC4626 token
*
* @param _component Address of the component
* @param _isERC4626 Boolean indicating if the component is an ERC4626 token
*/
function setERC4626Component(
address _component,
bool _isERC4626
) external onlyOwner {
erc4626Components[_component] = _isERC4626;
}
/**
* Approve spender to spend specific token on behalf of this contract
*
* @param _token Address of the token to approve
* @param _spender Address of the spender
* @param _allowance Amount to approve
*/
function approveToken(IERC20 _token, address _spender, uint256 _allowance) external onlyOwner {
_token.approve(_spender, _allowance);
}
/**
* Withdraw slippage to selected address
*
* @param _tokens Addresses of tokens to withdraw, specify ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(
IERC20[] calldata _tokens,
address payable _to
) external payable onlyOwner {
for (uint256 i = 0; i < _tokens.length; i++) {
if (address(_tokens[i]) == DEXAdapterV3.ETH_ADDRESS) {
_to.sendValue(address(this).balance);
} else {
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
/**
* Set swap data for specific token pair
*
* @param _inputToken Address of the input token
* @param _outputToken Address of the output token
* @param _swapData Swap data for the token pair describing DEX / route
*/
function setSwapData(
address _inputToken,
address _outputToken,
DEXAdapterV3.SwapData memory _swapData
) external onlyOwner {
swapData[_inputToken][_outputToken] = _swapData;
}
/**
* Set Pendle Market to use for specific pt including relevant metadata
*
* @param _pt Address of the Pendle Principal Token
* @param _sy Address of the corresponding Standardized Yield Token
* @param _underlying Address of the underlying token to redeem to
* @param _market Address of the Pendle Market to use for swapping between pt and sy
* @param _exchangeRateFactor Factor to multiply the exchange rate when supplying to Pendle Market
*/
function setPendleMarket(
IPendlePrincipalToken _pt,
IPendleStandardizedYield _sy,
address _underlying,
IPendleMarketV3 _market,
uint256 _exchangeRateFactor
) external onlyOwner {
pendleMarkets[_pt] = _market;
pendleMarketData[_market] = PendleMarketData({
pt: _pt,
sy: _sy,
underlying: _underlying,
exchangeRateFactor: _exchangeRateFactor
});
}
/**
* Callback method that is called by Pendle Market during the swap to request input token
*
* @param _ptToAccount Swap balance of pt token (negative -> swapping pt to sy)
* @param _syToAccount Swap balance of sy token (negative -> swapping sy to pt)
* @param _data Arbitrary data passed by Pendle Market (not used)
*/
function swapCallback(int256 _ptToAccount, int256 _syToAccount, bytes calldata _data) external {
PendleMarketData storage marketData = pendleMarketData[IPendleMarketV3(msg.sender)];
require(address(marketData.sy) != address(0), "ISC");
if (_ptToAccount < 0) {
uint256 ptAmount = uint256(-_ptToAccount);
marketData.pt.transfer(msg.sender, ptAmount);
} else if (_syToAccount < 0) {
uint256 syAmount = uint256(-_syToAccount);
// Withdraw necessary ETH, if deposit size is enough to move the oracle, then the exchange rate will not be
// valid for computing the amount of ETH to withdraw, so increase by exchangeRateFactor
uint256 ethAmount = syAmount.mul(marketData.sy.exchangeRate()).div(1 ether);
uint256 syAmountPreview = marketData.sy.previewDeposit(address(0), ethAmount);
if (syAmountPreview < syAmount) {
ethAmount = ethAmount * marketData.exchangeRateFactor / 1 ether;
}
// Special handling for agETH
if (marketData.underlying == address(agETH)) {
rsEthAdapter.getRSETHWithETH{value: ethAmount}("");
uint256 agEthAmount = agETH.balanceOf(address(this));
marketData.sy.deposit(address(this), address(agETH), agEthAmount, 0);
} else {
marketData.sy.deposit{ value: ethAmount }(address(this), address(0), ethAmount, 0);
}
marketData.sy.transfer(msg.sender, syAmount);
} else {
revert("Invalid callback");
}
}
/* ============ Internal ============ */
/**
* @dev Issue exact amount of SetToken from ETH
*
*/
function _issueExactSetFromEth(
ISetToken _setToken,
uint256 _amountSetToken,
DEXAdapterV3.SwapData[] memory _swapDataEthToComponent
) internal returns (uint256) {
(address[] memory components, uint256[] memory positions, ) = IDebtIssuanceModule(
issuanceModule
).getRequiredComponentIssuanceUnits(_setToken, _amountSetToken);
uint256 ethBalanceBefore = address(this).balance;
for (uint256 i = 0; i < components.length; i++) {
_depositIntoComponent(components[i], positions[i], _swapDataEthToComponent[i]);
}
issuanceModule.issue(_setToken, _amountSetToken, msg.sender);
return ethBalanceBefore.sub(address(this).balance);
}
/**
* @dev Redeem exact amount of SetToken for ETH
*
*/
function _redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minETHOut,
DEXAdapterV3.SwapData[] memory _swapDataComponentToEth
) internal returns (uint256) {
uint256 ethBalanceBefore = address(this).balance;
_setToken.safeTransferFrom(msg.sender, address(this), _amountSetToken);
issuanceModule.redeem(_setToken, _amountSetToken, address(this));
(address[] memory components, uint256[] memory positions, ) = IDebtIssuanceModule(
issuanceModule
).getRequiredComponentRedemptionUnits(_setToken, _amountSetToken);
for (uint256 i = 0; i < components.length; i++) {
_withdrawFromComponent(components[i], positions[i], _swapDataComponentToEth[i]);
}
return address(this).balance.sub(ethBalanceBefore);
}
/**
* @dev Deposit ETH into given component
*
*/
function _depositIntoComponent(
address _component,
uint256 _amount,
DEXAdapterV3.SwapData memory _swapData
) internal {
if(_swapData.exchange != DEXAdapterV3.Exchange.None) {
_swapEthForExactToken(_component, _amount, _swapData);
return;
}
if (_isInstadapp(_component)) {
_depositIntoInstadapp(IERC4626(_component), _amount);
return;
}
IPendleStandardizedYield syToken = _getSyToken(IPendlePrincipalToken(_component));
if (syToken != IPendleStandardizedYield(address(0))) {
_depositIntoPendle(IPendlePrincipalToken(_component), _amount, syToken);
return;
}
if (IERC20(_component) == acrossToken) {
_depositIntoAcross(_amount);
return;
}
if (_component == dexAdapter.weth) {
IWETH(dexAdapter.weth).deposit{ value: _amount }();
return;
}
if (erc4626Components[_component]) {
uint256 assetAmount = IERC4626(_component).previewMint(_amount);
address asset = IERC4626(_component).asset();
_swapEthForExactToken(asset, assetAmount, swapData[DEXAdapterV3.ETH_ADDRESS][asset]);
IERC4626(_component).mint(_amount, address(this));
return;
}
revert("Missing Swapdata for non-standard component");
}
/**
* @dev Withdraw ETH from given component
*
*/
function _withdrawFromComponent(
address _component,
uint256 _amount,
DEXAdapterV3.SwapData memory _swapData
) internal {
if(_swapData.exchange != DEXAdapterV3.Exchange.None) {
require(_swapData.path.length > 1, "zero length swap path");
require(_swapData.path[0] == _component, "Invalid input token");
require(_swapData.path[_swapData.path.length - 1] == DEXAdapterV3.ETH_ADDRESS || _swapData.path[_swapData.path.length - 1] == dexAdapter.weth, "Invalid output token");
uint256 ethReceived = dexAdapter.swapExactTokensForTokens(_amount, 0, _swapData);
if(_swapData.path[_swapData.path.length - 1] == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(ethReceived);
}
return;
}
if (_isInstadapp(_component)) {
_withdrawFromInstadapp(IERC4626(_component), _amount);
return;
}
IPendleMarketV3 market = pendleMarkets[IPendlePrincipalToken(_component)];
if (market != IPendleMarketV3(address(0))) {
_withdrawFromPendle(IPendlePrincipalToken(_component), _amount, market);
return;
}
if (IERC20(_component) == acrossToken) {
_withdrawFromAcross(_amount);
return;
}
if (_component == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(_amount);
return;
}
if (erc4626Components[_component]) {
address asset = IERC4626(_component).asset();
uint256 assetAmount = IERC4626(_component).redeem(_amount, address(this), address(this));
_swapExactTokenForEth(IERC20(asset), assetAmount, swapData[asset][DEXAdapterV3.ETH_ADDRESS]);
return;
}
revert("Missing Swapdata for non-standard component");
}
/**
* @dev Deposit eth into steth and then into instadapp vault
*
*/
function _depositIntoInstadapp(IERC4626 _vault, uint256 _amount) internal {
uint256 stETHAmount = _vault.previewMint(_amount);
_depositIntoLido(stETHAmount);
_vault.mint(_amount, address(this));
}
/**
* @dev Deposit eth into steth
*
*/
function _depositIntoLido(uint256 _amount) internal {
stETH.submit{ value: _amount }(address(0));
}
/**
* @dev Withdraw steth from instadapp vault and then swap to eth
* @dev Requires the respective swap data (stETH -> ETH) to be set
*
*/
function _withdrawFromInstadapp(IERC4626 _vault, uint256 _amount) internal {
uint256 stETHAmount = _vault.redeem(_amount, address(this), address(this));
_swapExactTokensForTokens(stETHAmount, address(stETH), address(0));
}
/**
* @dev Check if given component is the Instadapp vault
*
*/
function _isInstadapp(address _token) internal pure returns (bool) {
return _token == 0xA0D3707c569ff8C87FA923d3823eC5D81c98Be78;
}
/**
* @dev Get Sy token for given pt token
* @dev Also functions as check if given component is a Pendle Principal Token
*
*/
function _getSyToken(
IPendlePrincipalToken _pt
) internal view returns (IPendleStandardizedYield) {
return pendleMarketData[pendleMarkets[_pt]].sy;
}
/**
* @dev Initiate deposit into Pendle by swapping pt for sy
* @dev Deposit from eth to sy is done in swapCallback
*/
function _depositIntoPendle(
IPendlePrincipalToken _pt,
uint256 _ptAmount,
IPendleStandardizedYield _sy
) internal {
// Adding random bytes here since PendleMarket will not call back if data is empty
IPendleMarketV3(pendleMarkets[_pt]).swapSyForExactPt(address(this), _ptAmount, bytes("a"));
}
/**
* @dev Obtain across lp tokens by adding eth liquidity into the across pool
*/
function _depositIntoAcross(uint256 _acrossLpAmount) internal {
uint256 ethAmount = acrossPool
.exchangeRateCurrent(dexAdapter.weth)
.mul(_acrossLpAmount)
.div(1e18)
.add(ROUNDING_ERROR);
acrossPool.addLiquidity{ value: ethAmount }(dexAdapter.weth, ethAmount);
}
/**
* @dev Withdraw eth by removing liquidity from across pool
*/
function _withdrawFromAcross(uint256 _acrossLpAmount) internal {
acrossPool.removeLiquidity(dexAdapter.weth, _acrossLpAmount, true);
}
/**
* @dev Withdraw from Pendle by swapping pt for sy, redeeming sy for underlying and swapping underlying to eth
*/
function _withdrawFromPendle(
IPendlePrincipalToken _pt,
uint256 _ptAmount,
IPendleMarketV3 _pendleMarket
) internal {
// Adding random bytes here since PendleMarket will not call back if data is empty
(uint256 syAmount, ) = _pendleMarket.swapExactPtForSy(address(this), _ptAmount, bytes("a"));
PendleMarketData storage data = pendleMarketData[_pendleMarket];
uint256 amountUnderlying = data.sy.redeem(
address(this),
syAmount,
data.underlying,
0,
false
);
_swapExactTokensForTokens(amountUnderlying, data.underlying, address(0));
IWETH(dexAdapter.weth).withdraw(IERC20(dexAdapter.weth).balanceOf(address(this)));
}
/**
* @dev Swap exact amount of input token for output token using configured swap data
*/
function _swapExactTokensForTokens(
uint256 _amountIn,
address _inputToken,
address _outputToken
) internal returns (uint256) {
dexAdapter.swapExactTokensForTokens(_amountIn, 0, swapData[_inputToken][_outputToken]);
}
/**
* @dev Convert ETH to specified token, either swapping or simply depositing if outputToken is WETH
*/
function _swapFromEthToToken(
IERC20 _outputToken,
uint256 _ethAmount,
DEXAdapterV3.SwapData memory _swapDataEthToOutputToken
) internal returns(uint256 outputTokenAmount) {
if(address(_outputToken) == address(dexAdapter.weth)) {
outputTokenAmount = _ethAmount;
IWETH(dexAdapter.weth).deposit{value: _ethAmount}();
} else {
if(_swapDataEthToOutputToken.path[0] == dexAdapter.weth) {
IWETH(dexAdapter.weth).deposit{value: _ethAmount}();
}
outputTokenAmount = dexAdapter.swapExactTokensForTokens(
_ethAmount,
0,
_swapDataEthToOutputToken
);
}
}
/**
* @dev Convert specified token to ETH, either swapping or simply withdrawing if inputToken is WETH
*/
function _swapExactTokenForEth(
IERC20 _inputToken,
uint256 _inputTokenAmount,
DEXAdapterV3.SwapData memory _swapDataInputTokenToEth
) internal returns (uint256 ethAmount) {
if(address(_inputToken) == dexAdapter.weth) {
ethAmount = _inputTokenAmount;
IWETH(dexAdapter.weth).withdraw(ethAmount);
} else {
ethAmount = dexAdapter.swapExactTokensForTokens(
_inputTokenAmount,
0,
_swapDataInputTokenToEth
);
if(_swapDataInputTokenToEth.path[_swapDataInputTokenToEth.path.length - 1] == dexAdapter.weth) {
IWETH(dexAdapter.weth).withdraw(ethAmount);
}
}
}
function _swapEthForExactToken(address _token, uint256 _amount, DEXAdapterV3.SwapData memory _swapData) internal {
if(_token == dexAdapter.weth) {
IWETH(dexAdapter.weth).deposit{value: _amount}();
return;
}
require(_swapData.path.length > 1, "zero length swap path");
require(_swapData.path[0] == DEXAdapterV3.ETH_ADDRESS || _swapData.path[0] == dexAdapter.weth, "Invalid input token");
require(_swapData.path[_swapData.path.length - 1] == _token, "Invalid output token");
if(_swapData.path[0] == dexAdapter.weth) {
uint256 balanceBefore = IWETH(dexAdapter.weth).balanceOf(address(this));
IWETH(dexAdapter.weth).deposit{value: address(this).balance}();
dexAdapter.swapTokensForExactTokens(_amount, IWETH(dexAdapter.weth).balanceOf(address(this)), _swapData);
IWETH(dexAdapter.weth).withdraw(IWETH(dexAdapter.weth).balanceOf(address(this)).sub(balanceBefore));
}
else {
dexAdapter.swapTokensForExactTokens(_amount, address(this).balance, _swapData);
}
}
}
FlashMintLeveraged.sol 1251 lines
/*
Copyright 2025 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { IAaveLeverageModule } from "../interfaces/IAaveLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
import { FlashLoanReceiverBaseV2 } from "../../external/contracts/aaveV2/FlashLoanReceiverBaseV2.sol";
import { DEXAdapter } from "./DEXAdapter.sol";
import {IVault, IFlashLoanRecipient} from "../interfaces/external/balancer-v2/IVault.sol";
import {IPool} from "../interfaces/IPool.sol";
/**
* @title FlashMintLeveraged
* @author Index Coop
*
* Contract for issuing and redeeming a leveraged Set Token
* Supports all tokens with one collateral Position in the form of an AToken and one debt position
* Both the collateral as well as the debt token have to be available for flashloan from balancer and be
* tradeable against each other on Sushi / Quickswap
*/
contract FlashMintLeveraged is ReentrancyGuard, IFlashLoanRecipient{
using DEXAdapter for DEXAdapter.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralAToken;
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapter.SwapData collateralAndDebtSwapData;
DEXAdapter.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IAaveLeverageModule public immutable aaveLeverageModule;
DEXAdapter.Addresses public addresses;
IVault public immutable balancerV2Vault;
IPool public immutable LENDING_POOL;
address private flashLoanBenefactor;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier onlyBalancerV2Vault() {
require(msg.sender == address(balancerV2Vault), "ExchangeIssuance: BalancerV2 Vault ONLY");
_;
}
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapter.ETH_ADDRESS),
"ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapter.ETH_ADDRESS),
"ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _addresses dex adapter addreses
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _aaveV3Pool Address of address provider for aaves addresses
* @param _vault Balancer Vault to flashloan from
*/
constructor(
DEXAdapter.Addresses memory _addresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IAaveLeverageModule _aaveLeverageModule,
address _aaveV3Pool,
address _vault
)
public
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
aaveLeverageModule = _aaveLeverageModule;
addresses = _addresses;
LENDING_POOL = IPool(_aaveV3Pool);
balancerV2Vault = IVault(_vault);
}
/* ============ External Functions ============ */
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
external
view
returns (LeveragedTokenData memory)
{
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the input cost of issuing a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataDebtForCollateral swap data for the debt to collateral swap
* @param _swapDataInputToken swap data for the input token to collateral swap
*
* @return the amount of input tokens required to perfrom the issuance
*/
function getIssueExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory issueInfo = _getLeveragedTokenData(_setToken, _setAmount, true);
uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
uint256 borrowSaleProceeds = DEXAdapter.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
collateralOwed = collateralOwed.sub(borrowSaleProceeds);
return DEXAdapter.getAmountIn(addresses, _swapDataInputToken, collateralOwed);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataCollateralForDebt swap data for the collateral to debt swap
* @param _swapDataOutputToken swap data for the collateral token to the output token
*
* @return amount of _outputToken that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory redeemInfo = _getLeveragedTokenData(_setToken, _setAmount, false);
uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
uint256 debtPurchaseCost = DEXAdapter.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed);
uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
return DEXAdapter.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
virtual
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
virtual
payable
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* This is the callback function that will be called by the Balancerv2 Pool after flashloaned tokens have been sent
* to this contract.
* After exiting this function the Vault enforces that we transfer back the loaned tokens + interest. If that check fails
* the whole transaction gets reverted
*
* @param tokens Addresses of all assets that were borrowed
* @param amounts Amounts that were borrowed
* @param feeAmounts Interest to be paid on top of borrowed amount
* @param userData Encoded bytestring of other parameters from the original contract call to be used downstream
*
*/
function receiveFlashLoan(
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
)
external
override
onlyBalancerV2Vault
{
DecodedParams memory decodedParams = abi.decode(userData, (DecodedParams));
require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
if(decodedParams.isIssuance){
_performIssuance(address(tokens[0]), amounts[0], feeAmounts[0], decodedParams);
} else {
_performRedemption(address(tokens[0]), amounts[0], feeAmounts[0], decodedParams);
}
for(uint256 i = 0; i < tokens.length; i++) {
tokens[i].safeTransfer(address(balancerV2Vault), amounts[i]+ feeAmounts[i]);
}
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true);
_approveToken(IERC20(leveragedTokenData.collateralAToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
}
/* ============ Internal Functions ============ */
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _collateralToken Address of the underlying collateral token that was loaned
* @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
address _collateralToken,
uint256 _collateralTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Deposit collateral token obtained from flashloan to get the respective aToken position required for issuance
_depositCollateralToken(_collateralToken, _collateralTokenAmountNet);
// Issue set using the aToken returned by deposit step
_issueSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
uint amountInputTokenSpent = _obtainCollateralTokens(
_collateralToken,
_collateralTokenAmountNet + _premium,
_decodedParams
);
require(amountInputTokenSpent <= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT INPUT AMOUNT");
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _debtToken Address of the debt token that was loaned
* @param _debtTokenAmountNet Amount of debt token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
address _debtToken,
uint256 _debtTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Redeem set using debt tokens obtained from flashloan
_redeemSet(
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender
);
// Withdraw underlying collateral token from the aToken position returned by redeem step
_withdrawCollateralToken(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - ROUNDING_ERROR_MARGIN
);
// Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
uint256 collateralTokenSpent = _swapCollateralForDebtToken(
_debtTokenAmountNet + _premium,
_debtToken,
_decodedParams.leveragedTokenData.collateralAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Liquidate remaining collateral tokens for the payment token specified by user
uint256 amountOutputToken = _liquidateCollateralTokens(
collateralTokenSpent,
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender,
_decodedParams.paymentToken,
_decodedParams.limitAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
_decodedParams.paymentTokenSwapData
);
require(amountOutputToken >= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(equityPositions[0] == 0 || equityPositions[1] == 0, "ExchangeIssuance: TOO MANY EQUITY POSITIONS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
return LeveragedTokenData(
components[0],
IAToken(components[0]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
components[1],
IAToken(components[1]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true);
address[] memory assets = new address[](1);
assets[0] = leveragedTokenData.collateralToken;
uint[] memory amounts = new uint[](1);
amounts[0] = leveragedTokenData.collateralAmount;
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(assets, amounts, params);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false);
address[] memory assets = new address[](1);
assets[0] = leveragedTokenData.debtToken;
uint[] memory amounts = new uint[](1);
amounts[0] = leveragedTokenData.debtAmount;
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(assets, amounts, params);
}
/**
* Gets rid of the obtained collateral tokens from redemption by either sending them to the user
* directly or converting them to the payment token and sending those out.
*
* @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Address of the user who initiated the redemption
* @param _outputToken Address of token to return to the user
* @param _collateralToken Address of the collateral token to sell
* @param _collateralAmount Amount of collateral token to sell
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapter.SwapData memory _swapData
)
internal
returns (uint256)
{
require(_collateralAmount >= _collateralTokenSpent, "ExchangeIssuance: OVERSPENT COLLATERAL TOKEN");
uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
uint256 outputAmount;
if(_outputToken == DEXAdapter.ETH_ADDRESS){
outputAmount = _liquidateCollateralTokensForETH(
_collateralToken,
amountToReturn,
_originalSender,
_minAmountOutputToken,
_swapData
);
} else {
outputAmount = _liquidateCollateralTokensForERC20(
_collateralToken,
amountToReturn,
_originalSender,
IERC20(_outputToken),
_minAmountOutputToken,
_swapData
);
}
emit FlashMint(_originalSender, _setToken, _outputToken, _setAmount, outputAmount);
return outputAmount;
}
/**
* Returns the collateralToken directly to the user
*
* @param _collateralToken Address of the the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
*/
function _returnCollateralTokensToSender(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender
)
internal
{
IERC20(_collateralToken).safeTransfer(_originalSender, _collateralRemaining);
}
/**
* Sells the collateral tokens for the selected output ERC20 and returns that to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to Output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
_returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
_outputToken.safeTransfer(_originalSender, outputTokenAmount);
return outputTokenAmount;
}
/**
* Sells the remaining collateral tokens for weth, withdraws that and returns native eth to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the eth to
* @param _minAmountOutputToken Minimum amount of output token to return to user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to eth
*
* @return Amount of eth returned to the user
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
virtual
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
if (ethAmount > 0) {
IWETH(addresses.weth).withdraw(ethAmount);
(payable(_originalSender)).sendValue(ethAmount);
}
return ethAmount;
}
/**
* Obtains the tokens necessary to return the flashloan by swapping the debt tokens obtained
* from issuance and making up the shortfall using the users funds.
*
* @param _collateralToken collateral token to obtain
* @param _amountRequired Amount of collateralToken required to repay the flashloan
* @param _decodedParams Struct containing decoded data from original call passed through via flashloan
*
* @return Amount of input token spent
*/
function _obtainCollateralTokens(
address _collateralToken,
uint256 _amountRequired,
DecodedParams memory _decodedParams
)
internal
returns (uint256)
{
uint collateralTokenObtained = _swapDebtForCollateralToken(
_collateralToken,
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.leveragedTokenData.debtAmount,
_decodedParams.collateralAndDebtSwapData
);
uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
uint amountInputToken;
if(_decodedParams.paymentToken == DEXAdapter.ETH_ADDRESS){
amountInputToken = _makeUpShortfallWithETH(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
} else {
amountInputToken = _makeUpShortfallWithERC20(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
IERC20(_decodedParams.paymentToken),
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
}
emit FlashRedeem(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountInputToken,
_decodedParams.setAmount
);
return amountInputToken;
}
/**
* Issues set token using the previously obtained collateral token
* Results in debt token being returned to the contract
*
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Adress that initiated the token issuance, which will receive the set tokens
*/
function _issueSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
}
/**
* Redeems set token using the previously obtained debt token
* Results in collateral token being returned to the contract
*
* @param _setToken Address of the SetToken to be redeemed
* @param _setAmount Amount of SetTokens to redeem
* @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
* from swapping the debt tokens from the users address
*
* @param _token Address of the token to transfer from user
* @param _shortfall Collateral token shortfall required to return the flashloan
* @param _originalSender Adress that initiated the token issuance, which is the adresss form which to transfer the tokens
*/
function _transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
internal
{
if(_shortfall>0){
IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
}
}
/**
* Makes up the collateral token shortfall with user specified ERC20 token
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
*
* @return Amount of input token spent
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
_transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
return _collateralTokenShortfall;
} else {
_inputToken.safeTransferFrom(_originalSender, address(this), _maxAmountInputToken);
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
if(amountInputToken < _maxAmountInputToken){
_inputToken.safeTransfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
}
return amountInputToken;
}
}
/**
* Makes up the collateral token shortfall with native eth
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _maxAmountEth Maximum amount of eth to pay
*
* @return Amount of eth spent
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapter.SwapData memory _swapData
)
internal
virtual
returns(uint256)
{
IWETH(addresses.weth).deposit{value: _maxAmountEth}();
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
if(_maxAmountEth > amountEth){
uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
IWETH(addresses.weth).withdraw(amountEthReturn);
(payable(_originalSender)).sendValue(amountEthReturn);
}
return amountEth;
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _collateralToken Address of the collateral token buy
* @param _debtToken Address of the debt token to sell
* @param _debtAmount Amount of debt token to sell
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token obtained
*/
function _swapDebtForCollateralToken(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _debtToken, _collateralToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_debtAmount,
// minAmountOut is 0 here since we are going to make up the shortfall with the input token.
// Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
0,
_swapData
);
}
/**
* Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption
*
* @param _debtAmount Amount of debt token to buy
* @param _debtToken Address of debt token
* @param _collateralAmount Amount of collateral token available to spend / used as maxAmountIn parameter
* @param _collateralToken Address of collateral token
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token spent
*/
function _swapCollateralForDebtToken(
uint256 _debtAmount,
address _debtToken,
uint256 _collateralAmount,
address _collateralToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _debtToken)
returns (uint256)
{
return addresses.swapTokensForExactTokens(
_debtAmount,
_collateralAmount,
_swapData
);
}
/**
* Acquires the required amount of collateral tokens by swapping the input tokens
* Does nothing if collateral and input token are indentical
*
* @param _collateralToken Address of collateral token
* @param _amountRequired Remaining amount of collateral token required to repay flashloan, after having swapped debt tokens for collateral
* @param _inputToken Address of input token to swap
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of input token spent
*/
function _swapInputForCollateralToken(
address _collateralToken,
uint256 _amountRequired,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(
_swapData.path,
_inputToken,
_collateralToken
)
returns (uint256)
{
if(_collateralToken == _inputToken) return _amountRequired;
return addresses.swapTokensForExactTokens(
_amountRequired,
_maxAmountInputToken,
_swapData
);
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Address of collateral token
* @param _collateralTokenAmount Amount of colalteral token to swap
* @param _outputToken Address of the ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Deposit collateral to aave to obtain collateralAToken for issuance
*
* @param _collateralToken Address of collateral token
* @param _depositAmount Amount to deposit
*/
function _depositCollateralToken(
address _collateralToken,
uint256 _depositAmount
) internal {
LENDING_POOL.deposit(_collateralToken, _depositAmount, address(this), 0);
}
/**
* Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
*
* @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
* @param _collateralAmount Amount of collateralToken to withdraw
*/
function _withdrawCollateralToken(
address _collateralToken,
uint256 _collateralAmount
) internal {
LENDING_POOL.withdraw(_collateralToken, _collateralAmount, address(this));
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Approves max amount of token to lending pool
*
* @param _token Address of the token to approve
*/
function _approveTokenToLendingPool(
IERC20 _token
)
internal
{
uint256 allowance = _token.allowance(address(this), address(LENDING_POOL));
if (allowance > 0) {
_safeApprove(_token, address(LENDING_POOL), 0);
}
_safeApprove(_token, address(LENDING_POOL), MAX_UINT256);
}
/**
* Triggers the flashloan from the BalancerV2 Vault
*
* @param assets Addresses of tokens to loan
* @param amounts Amounts to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address[] memory assets,
uint256[] memory amounts,
bytes memory params
)
internal
{
require(flashLoanBenefactor == address(0), "Flashloan already taken");
flashLoanBenefactor = msg.sender;
balancerV2Vault.flashLoan(this, assets, amounts, params);
flashLoanBenefactor = address(0);
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
debtIssuanceModule.redeem(_setToken, _amount, address(this));
}
receive() external payable {}
}
FlashMintLeveragedAaveFL.sol 1236 lines
/*
Copyright 2025 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { IAaveLeverageModule } from "../interfaces/IAaveLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { FlashLoanSimpleReceiverBase } from "../lib/FlashLoanSimpleReceiverBase.sol";
import { IPoolAddressesProvider } from "../interfaces/IPoolAddressesProvider.sol";
import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
import { DEXAdapter } from "./DEXAdapter.sol";
import {IPool} from "../interfaces/IPool.sol";
/**
* @title FlashMintLeveraged
* @author Index Coop
*
* Contract for issuing and redeeming a leveraged Set Token
* Supports all tokens with one collateral Position in the form of an AToken and one debt position
* Both the collateral as well as the debt token have to be available for flashloan from aave and be
* tradeable against each other on Sushi / Quickswap
*/
contract FlashMintLeveragedAaveFL is ReentrancyGuard, FlashLoanSimpleReceiverBase {
using DEXAdapter for DEXAdapter.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralAToken;
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapter.SwapData collateralAndDebtSwapData;
DEXAdapter.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IAaveLeverageModule public immutable aaveLeverageModule;
DEXAdapter.Addresses public addresses;
address private flashLoanBenefactor;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier onlyAavePool() {
require(msg.sender == address(POOL), "ExchangeIssuance: Aave Pool ONLY");
_;
}
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapter.ETH_ADDRESS),
"ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapter.ETH_ADDRESS),
"ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _addresses dex adapter addreses
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _addressProvider Address of address provider for aaves addresses
*/
constructor(
DEXAdapter.Addresses memory _addresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IAaveLeverageModule _aaveLeverageModule,
IPoolAddressesProvider _addressProvider
)
FlashLoanSimpleReceiverBase(_addressProvider)
public
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
aaveLeverageModule = _aaveLeverageModule;
addresses = _addresses;
}
/* ============ External Functions ============ */
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
external
view
returns (LeveragedTokenData memory)
{
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the input cost of issuing a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataDebtForCollateral swap data for the debt to collateral swap
* @param _swapDataInputToken swap data for the input token to collateral swap
*
* @return the amount of input tokens required to perfrom the issuance
*/
function getIssueExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory issueInfo = _getLeveragedTokenData(_setToken, _setAmount, true);
uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
uint256 borrowSaleProceeds = DEXAdapter.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
collateralOwed = collateralOwed.sub(borrowSaleProceeds);
return DEXAdapter.getAmountIn(addresses, _swapDataInputToken, collateralOwed);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataCollateralForDebt swap data for the collateral to debt swap
* @param _swapDataOutputToken swap data for the collateral token to the output token
*
* @return amount of _outputToken that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory redeemInfo = _getLeveragedTokenData(_setToken, _setAmount, false);
uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
uint256 debtPurchaseCost = DEXAdapter.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed);
uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
return DEXAdapter.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
virtual
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
virtual
payable
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* This is the callback function that will be called by the Aave Pool after flashloaned tokens have been sent
* to this contract.
* After exiting this function the Pool transfers back the loaned tokens + interest. If that transfer fails
* the whole transaction gets reverted
*/
function executeOperation(
address asset, // asset
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
)
external
override
onlyAavePool
returns(bool)
{
DecodedParams memory decodedParams = abi.decode(params, (DecodedParams));
require(initiator == address(this), "Flashloan not initiated by this contract");
require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
if(decodedParams.isIssuance){
_performIssuance(asset, amount, premium, decodedParams);
} else {
_performRedemption(asset, amount, premium, decodedParams);
}
return true;
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true);
_approveToken(IERC20(leveragedTokenData.collateralAToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
}
/* ============ Internal Functions ============ */
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _collateralToken Address of the underlying collateral token that was loaned
* @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
address _collateralToken,
uint256 _collateralTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Deposit collateral token obtained from flashloan to get the respective aToken position required for issuance
_depositCollateralToken(_collateralToken, _collateralTokenAmountNet);
// Issue set using the aToken returned by deposit step
_issueSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
uint amountInputTokenSpent = _obtainCollateralTokens(
_collateralToken,
_collateralTokenAmountNet + _premium,
_decodedParams
);
require(amountInputTokenSpent <= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT INPUT AMOUNT");
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _debtToken Address of the debt token that was loaned
* @param _debtTokenAmountNet Amount of debt token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
address _debtToken,
uint256 _debtTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Redeem set using debt tokens obtained from flashloan
_redeemSet(
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender
);
// Withdraw underlying collateral token from the aToken position returned by redeem step
_withdrawCollateralToken(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - ROUNDING_ERROR_MARGIN
);
// Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
uint256 collateralTokenSpent = _swapCollateralForDebtToken(
_debtTokenAmountNet + _premium,
_debtToken,
_decodedParams.leveragedTokenData.collateralAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Liquidate remaining collateral tokens for the payment token specified by user
uint256 amountOutputToken = _liquidateCollateralTokens(
collateralTokenSpent,
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender,
_decodedParams.paymentToken,
_decodedParams.limitAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
_decodedParams.paymentTokenSwapData
);
require(amountOutputToken >= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(equityPositions[0] == 0 || equityPositions[1] == 0, "ExchangeIssuance: TOO MANY EQUITY POSITIONS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
return LeveragedTokenData(
components[0],
IAToken(components[0]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
components[1],
IAToken(components[1]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(leveragedTokenData.collateralToken, leveragedTokenData.collateralAmount, params);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(leveragedTokenData.debtToken, leveragedTokenData.debtAmount, params);
}
/**
* Gets rid of the obtained collateral tokens from redemption by either sending them to the user
* directly or converting them to the payment token and sending those out.
*
* @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Address of the user who initiated the redemption
* @param _outputToken Address of token to return to the user
* @param _collateralToken Address of the collateral token to sell
* @param _collateralAmount Amount of collateral token to sell
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapter.SwapData memory _swapData
)
internal
returns (uint256)
{
require(_collateralAmount >= _collateralTokenSpent, "ExchangeIssuance: OVERSPENT COLLATERAL TOKEN");
uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
uint256 outputAmount;
if(_outputToken == DEXAdapter.ETH_ADDRESS){
outputAmount = _liquidateCollateralTokensForETH(
_collateralToken,
amountToReturn,
_originalSender,
_minAmountOutputToken,
_swapData
);
} else {
outputAmount = _liquidateCollateralTokensForERC20(
_collateralToken,
amountToReturn,
_originalSender,
IERC20(_outputToken),
_minAmountOutputToken,
_swapData
);
}
emit FlashMint(_originalSender, _setToken, _outputToken, _setAmount, outputAmount);
return outputAmount;
}
/**
* Returns the collateralToken directly to the user
*
* @param _collateralToken Address of the the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
*/
function _returnCollateralTokensToSender(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender
)
internal
{
IERC20(_collateralToken).safeTransfer(_originalSender, _collateralRemaining);
}
/**
* Sells the collateral tokens for the selected output ERC20 and returns that to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to Output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
_returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
_outputToken.safeTransfer(_originalSender, outputTokenAmount);
return outputTokenAmount;
}
/**
* Sells the remaining collateral tokens for weth, withdraws that and returns native eth to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the eth to
* @param _minAmountOutputToken Minimum amount of output token to return to user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to eth
*
* @return Amount of eth returned to the user
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
virtual
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
if (ethAmount > 0) {
IWETH(addresses.weth).withdraw(ethAmount);
(payable(_originalSender)).sendValue(ethAmount);
}
return ethAmount;
}
/**
* Obtains the tokens necessary to return the flashloan by swapping the debt tokens obtained
* from issuance and making up the shortfall using the users funds.
*
* @param _collateralToken collateral token to obtain
* @param _amountRequired Amount of collateralToken required to repay the flashloan
* @param _decodedParams Struct containing decoded data from original call passed through via flashloan
*
* @return Amount of input token spent
*/
function _obtainCollateralTokens(
address _collateralToken,
uint256 _amountRequired,
DecodedParams memory _decodedParams
)
internal
returns (uint256)
{
uint collateralTokenObtained = _swapDebtForCollateralToken(
_collateralToken,
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.leveragedTokenData.debtAmount,
_decodedParams.collateralAndDebtSwapData
);
uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
uint amountInputToken;
if(_decodedParams.paymentToken == DEXAdapter.ETH_ADDRESS){
amountInputToken = _makeUpShortfallWithETH(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
} else {
amountInputToken = _makeUpShortfallWithERC20(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
IERC20(_decodedParams.paymentToken),
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
}
emit FlashRedeem(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountInputToken,
_decodedParams.setAmount
);
return amountInputToken;
}
/**
* Issues set token using the previously obtained collateral token
* Results in debt token being returned to the contract
*
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Adress that initiated the token issuance, which will receive the set tokens
*/
function _issueSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
}
/**
* Redeems set token using the previously obtained debt token
* Results in collateral token being returned to the contract
*
* @param _setToken Address of the SetToken to be redeemed
* @param _setAmount Amount of SetTokens to redeem
* @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
* from swapping the debt tokens from the users address
*
* @param _token Address of the token to transfer from user
* @param _shortfall Collateral token shortfall required to return the flashloan
* @param _originalSender Adress that initiated the token issuance, which is the adresss form which to transfer the tokens
*/
function _transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
internal
{
if(_shortfall>0){
IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
}
}
/**
* Makes up the collateral token shortfall with user specified ERC20 token
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
*
* @return Amount of input token spent
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
_transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
return _collateralTokenShortfall;
} else {
_inputToken.safeTransferFrom(_originalSender, address(this), _maxAmountInputToken);
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
if(amountInputToken < _maxAmountInputToken){
_inputToken.safeTransfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
}
return amountInputToken;
}
}
/**
* Makes up the collateral token shortfall with native eth
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _maxAmountEth Maximum amount of eth to pay
*
* @return Amount of eth spent
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapter.SwapData memory _swapData
)
internal
virtual
returns(uint256)
{
IWETH(addresses.weth).deposit{value: _maxAmountEth}();
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
if(_maxAmountEth > amountEth){
uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
IWETH(addresses.weth).withdraw(amountEthReturn);
(payable(_originalSender)).sendValue(amountEthReturn);
}
return amountEth;
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _collateralToken Address of the collateral token buy
* @param _debtToken Address of the debt token to sell
* @param _debtAmount Amount of debt token to sell
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token obtained
*/
function _swapDebtForCollateralToken(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _debtToken, _collateralToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_debtAmount,
// minAmountOut is 0 here since we are going to make up the shortfall with the input token.
// Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
0,
_swapData
);
}
/**
* Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption
*
* @param _debtAmount Amount of debt token to buy
* @param _debtToken Address of debt token
* @param _collateralAmount Amount of collateral token available to spend / used as maxAmountIn parameter
* @param _collateralToken Address of collateral token
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token spent
*/
function _swapCollateralForDebtToken(
uint256 _debtAmount,
address _debtToken,
uint256 _collateralAmount,
address _collateralToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _debtToken)
returns (uint256)
{
return addresses.swapTokensForExactTokens(
_debtAmount,
_collateralAmount,
_swapData
);
}
/**
* Acquires the required amount of collateral tokens by swapping the input tokens
* Does nothing if collateral and input token are indentical
*
* @param _collateralToken Address of collateral token
* @param _amountRequired Remaining amount of collateral token required to repay flashloan, after having swapped debt tokens for collateral
* @param _inputToken Address of input token to swap
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of input token spent
*/
function _swapInputForCollateralToken(
address _collateralToken,
uint256 _amountRequired,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(
_swapData.path,
_inputToken,
_collateralToken
)
returns (uint256)
{
if(_collateralToken == _inputToken) return _amountRequired;
return addresses.swapTokensForExactTokens(
_amountRequired,
_maxAmountInputToken,
_swapData
);
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Address of collateral token
* @param _collateralTokenAmount Amount of colalteral token to swap
* @param _outputToken Address of the ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Deposit collateral to aave to obtain collateralAToken for issuance
*
* @param _collateralToken Address of collateral token
* @param _depositAmount Amount to deposit
*/
function _depositCollateralToken(
address _collateralToken,
uint256 _depositAmount
) internal {
POOL.deposit(_collateralToken, _depositAmount, address(this), 0);
}
/**
* Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
*
* @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
* @param _collateralAmount Amount of collateralToken to withdraw
*/
function _withdrawCollateralToken(
address _collateralToken,
uint256 _collateralAmount
) internal {
POOL.withdraw(_collateralToken, _collateralAmount, address(this));
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Approves max amount of token to lending pool
*
* @param _token Address of the token to approve
*/
function _approveTokenToLendingPool(
IERC20 _token
)
internal
{
uint256 allowance = _token.allowance(address(this), address(POOL));
if (allowance > 0) {
_safeApprove(_token, address(POOL), 0);
}
_safeApprove(_token, address(POOL), MAX_UINT256);
}
/**
* Triggers the flashloan from the AaveV3 Pool
*
* @param asset Address of token to loan
* @param amount Amount to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address asset,
uint256 amount,
bytes memory params
)
internal
{
require(flashLoanBenefactor == address(0), "Flashloan already taken");
flashLoanBenefactor = msg.sender;
POOL.flashLoanSimple(
address(this),
asset,
amount,
params,
0
);
flashLoanBenefactor = address(0);
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
debtIssuanceModule.redeem(_setToken, _amount, address(this));
}
receive() external payable {}
}
FlashMintLeveragedAerodrome.sol 1254 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { IAaveLeverageModule } from "../interfaces/IAaveLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
import { FlashLoanReceiverBaseV2 } from "../../external/contracts/aaveV2/FlashLoanReceiverBaseV2.sol";
import { DEXAdapterV4 } from "./DEXAdapterV4.sol";
import {IVault, IFlashLoanRecipient} from "../interfaces/external/balancer-v2/IVault.sol";
import {IPool} from "../interfaces/IPool.sol";
/**
* @title FlashMintLeveraged
* @author Index Coop
*
* Contract for issuing and redeeming a leveraged Set Token
* Supports all tokens with one collateral Position in the form of an AToken and one debt position
* Both the collateral as well as the debt token have to be available for flashloan from balancer and be
* tradeable against each other on Sushi / Quickswap
* Uses DexAdapterV4 for Aerodrome Support
*/
contract FlashMintLeveragedAerodrome is ReentrancyGuard, IFlashLoanRecipient{
using DEXAdapterV4 for DEXAdapterV4.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralAToken;
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapterV4.SwapData collateralAndDebtSwapData;
DEXAdapterV4.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IAaveLeverageModule public immutable aaveLeverageModule;
DEXAdapterV4.Addresses public addresses;
IVault public immutable balancerV2Vault;
IPool public immutable LENDING_POOL;
address private flashLoanBenefactor;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier onlyBalancerV2Vault() {
require(msg.sender == address(balancerV2Vault), "ExchangeIssuance: BalancerV2 Vault ONLY");
_;
}
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapterV4.ETH_ADDRESS),
"ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapterV4.ETH_ADDRESS),
"ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _addresses dex adapter addreses
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _aaveV3Pool Address of address provider for aaves addresses
* @param _vault Balancer Vault to flashloan from
*/
constructor(
DEXAdapterV4.Addresses memory _addresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IAaveLeverageModule _aaveLeverageModule,
address _aaveV3Pool,
address _vault
)
public
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
aaveLeverageModule = _aaveLeverageModule;
addresses = _addresses;
LENDING_POOL = IPool(_aaveV3Pool);
balancerV2Vault = IVault(_vault);
}
/* ============ External Functions ============ */
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
external
view
returns (LeveragedTokenData memory)
{
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the input cost of issuing a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _maxAmountInputToken maximum amount of input token to spend
* @param _swapDataDebtForCollateral swap data for the debt to collateral swap
* @param _swapDataInputToken swap data for the input token to collateral swap
*
* @return the amount of input tokens required to perfrom the issuance
*/
function getIssueExactSet(
ISetToken _setToken,
uint256 _setAmount,
uint256 _maxAmountInputToken,
DEXAdapterV4.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV4.SwapData memory _swapDataInputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory issueInfo = _getLeveragedTokenData(_setToken, _setAmount, true);
uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
uint256 borrowSaleProceeds = DEXAdapterV4.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
collateralOwed = collateralOwed.sub(borrowSaleProceeds);
return DEXAdapterV4.getAmountIn(addresses, _swapDataInputToken, collateralOwed, _maxAmountInputToken);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataCollateralForDebt swap data for the collateral to debt swap
* @param _swapDataOutputToken swap data for the collateral token to the output token
*
* @return amount of _outputToken that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapterV4.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV4.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory redeemInfo = _getLeveragedTokenData(_setToken, _setAmount, false);
uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
uint256 debtPurchaseCost = DEXAdapterV4.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed, redeemInfo.collateralAmount);
uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
return DEXAdapterV4.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapterV4.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV4.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
DEXAdapterV4.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV4.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV4.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV4.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV4.SwapData memory _swapDataInputToken
)
external
virtual
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapterV4.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV4.SwapData memory _swapDataInputToken
)
external
virtual
payable
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
DEXAdapterV4.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* This is the callback function that will be called by the Balancerv2 Pool after flashloaned tokens have been sent
* to this contract.
* After exiting this function the Vault enforces that we transfer back the loaned tokens + interest. If that check fails
* the whole transaction gets reverted
*
* @param tokens Addresses of all assets that were borrowed
* @param amounts Amounts that were borrowed
* @param feeAmounts Interest to be paid on top of borrowed amount
* @param userData Encoded bytestring of other parameters from the original contract call to be used downstream
*
*/
function receiveFlashLoan(
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
)
external
override
onlyBalancerV2Vault
{
DecodedParams memory decodedParams = abi.decode(userData, (DecodedParams));
require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
if(decodedParams.isIssuance){
_performIssuance(address(tokens[0]), amounts[0], feeAmounts[0], decodedParams);
} else {
_performRedemption(address(tokens[0]), amounts[0], feeAmounts[0], decodedParams);
}
for(uint256 i = 0; i < tokens.length; i++) {
tokens[i].safeTransfer(address(balancerV2Vault), amounts[i]+ feeAmounts[i]);
}
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true);
_approveToken(IERC20(leveragedTokenData.collateralAToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
}
/* ============ Internal Functions ============ */
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _collateralToken Address of the underlying collateral token that was loaned
* @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
address _collateralToken,
uint256 _collateralTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Deposit collateral token obtained from flashloan to get the respective aToken position required for issuance
_depositCollateralToken(_collateralToken, _collateralTokenAmountNet);
// Issue set using the aToken returned by deposit step
_issueSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
uint amountInputTokenSpent = _obtainCollateralTokens(
_collateralToken,
_collateralTokenAmountNet + _premium,
_decodedParams
);
require(amountInputTokenSpent <= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT INPUT AMOUNT");
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _debtToken Address of the debt token that was loaned
* @param _debtTokenAmountNet Amount of debt token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
address _debtToken,
uint256 _debtTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Redeem set using debt tokens obtained from flashloan
_redeemSet(
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender
);
// Withdraw underlying collateral token from the aToken position returned by redeem step
_withdrawCollateralToken(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - ROUNDING_ERROR_MARGIN
);
// Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
uint256 collateralTokenSpent = _swapCollateralForDebtToken(
_debtTokenAmountNet + _premium,
_debtToken,
_decodedParams.leveragedTokenData.collateralAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Liquidate remaining collateral tokens for the payment token specified by user
uint256 amountOutputToken = _liquidateCollateralTokens(
collateralTokenSpent,
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender,
_decodedParams.paymentToken,
_decodedParams.limitAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
_decodedParams.paymentTokenSwapData
);
require(amountOutputToken >= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(equityPositions[0] == 0 || equityPositions[1] == 0, "ExchangeIssuance: TOO MANY EQUITY POSITIONS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
return LeveragedTokenData(
components[0],
IAToken(components[0]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
components[1],
IAToken(components[1]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV4.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV4.SwapData memory _swapDataInputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true);
address[] memory assets = new address[](1);
assets[0] = leveragedTokenData.collateralToken;
uint[] memory amounts = new uint[](1);
amounts[0] = leveragedTokenData.collateralAmount;
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(assets, amounts, params);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV4.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV4.SwapData memory _swapDataOutputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false);
address[] memory assets = new address[](1);
assets[0] = leveragedTokenData.debtToken;
uint[] memory amounts = new uint[](1);
amounts[0] = leveragedTokenData.debtAmount;
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(assets, amounts, params);
}
/**
* Gets rid of the obtained collateral tokens from redemption by either sending them to the user
* directly or converting them to the payment token and sending those out.
*
* @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Address of the user who initiated the redemption
* @param _outputToken Address of token to return to the user
* @param _collateralToken Address of the collateral token to sell
* @param _collateralAmount Amount of collateral token to sell
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapterV4.SwapData memory _swapData
)
internal
returns (uint256)
{
require(_collateralAmount >= _collateralTokenSpent, "ExchangeIssuance: OVERSPENT COLLATERAL TOKEN");
uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
uint256 outputAmount;
if(_outputToken == DEXAdapterV4.ETH_ADDRESS){
outputAmount = _liquidateCollateralTokensForETH(
_collateralToken,
amountToReturn,
_originalSender,
_minAmountOutputToken,
_swapData
);
} else {
outputAmount = _liquidateCollateralTokensForERC20(
_collateralToken,
amountToReturn,
_originalSender,
IERC20(_outputToken),
_minAmountOutputToken,
_swapData
);
}
emit FlashMint(_originalSender, _setToken, _outputToken, _setAmount, outputAmount);
return outputAmount;
}
/**
* Returns the collateralToken directly to the user
*
* @param _collateralToken Address of the the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
*/
function _returnCollateralTokensToSender(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender
)
internal
{
IERC20(_collateralToken).transfer(_originalSender, _collateralRemaining);
}
/**
* Sells the collateral tokens for the selected output ERC20 and returns that to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to Output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV4.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
_returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
_outputToken.transfer(_originalSender, outputTokenAmount);
return outputTokenAmount;
}
/**
* Sells the remaining collateral tokens for weth, withdraws that and returns native eth to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the eth to
* @param _minAmountOutputToken Minimum amount of output token to return to user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to eth
*
* @return Amount of eth returned to the user
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapterV4.SwapData memory _swapData
)
internal
virtual
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
if (ethAmount > 0) {
IWETH(addresses.weth).withdraw(ethAmount);
(payable(_originalSender)).sendValue(ethAmount);
}
return ethAmount;
}
/**
* Obtains the tokens necessary to return the flashloan by swapping the debt tokens obtained
* from issuance and making up the shortfall using the users funds.
*
* @param _collateralToken collateral token to obtain
* @param _amountRequired Amount of collateralToken required to repay the flashloan
* @param _decodedParams Struct containing decoded data from original call passed through via flashloan
*
* @return Amount of input token spent
*/
function _obtainCollateralTokens(
address _collateralToken,
uint256 _amountRequired,
DecodedParams memory _decodedParams
)
internal
returns (uint256)
{
uint collateralTokenObtained = _swapDebtForCollateralToken(
_collateralToken,
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.leveragedTokenData.debtAmount,
_decodedParams.collateralAndDebtSwapData
);
uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
uint amountInputToken;
if(_decodedParams.paymentToken == DEXAdapterV4.ETH_ADDRESS){
amountInputToken = _makeUpShortfallWithETH(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
} else {
amountInputToken = _makeUpShortfallWithERC20(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
IERC20(_decodedParams.paymentToken),
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
}
emit FlashRedeem(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountInputToken,
_decodedParams.setAmount
);
return amountInputToken;
}
/**
* Issues set token using the previously obtained collateral token
* Results in debt token being returned to the contract
*
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Adress that initiated the token issuance, which will receive the set tokens
*/
function _issueSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
}
/**
* Redeems set token using the previously obtained debt token
* Results in collateral token being returned to the contract
*
* @param _setToken Address of the SetToken to be redeemed
* @param _setAmount Amount of SetTokens to redeem
* @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
* from swapping the debt tokens from the users address
*
* @param _token Address of the token to transfer from user
* @param _shortfall Collateral token shortfall required to return the flashloan
* @param _originalSender Adress that initiated the token issuance, which is the adresss form which to transfer the tokens
*/
function _transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
internal
{
if(_shortfall>0){
IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
}
}
/**
* Makes up the collateral token shortfall with user specified ERC20 token
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
*
* @return Amount of input token spent
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV4.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
_transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
return _collateralTokenShortfall;
} else {
_inputToken.transferFrom(_originalSender, address(this), _maxAmountInputToken);
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
if(amountInputToken < _maxAmountInputToken){
_inputToken.transfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
}
return amountInputToken;
}
}
/**
* Makes up the collateral token shortfall with native eth
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _maxAmountEth Maximum amount of eth to pay
*
* @return Amount of eth spent
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapterV4.SwapData memory _swapData
)
internal
virtual
returns(uint256)
{
IWETH(addresses.weth).deposit{value: _maxAmountEth}();
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
if(_maxAmountEth > amountEth){
uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
IWETH(addresses.weth).withdraw(amountEthReturn);
(payable(_originalSender)).sendValue(amountEthReturn);
}
return amountEth;
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _collateralToken Address of the collateral token buy
* @param _debtToken Address of the debt token to sell
* @param _debtAmount Amount of debt token to sell
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token obtained
*/
function _swapDebtForCollateralToken(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
DEXAdapterV4.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _debtToken, _collateralToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_debtAmount,
// minAmountOut is 0 here since we are going to make up the shortfall with the input token.
// Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
0,
_swapData
);
}
/**
* Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption
*
* @param _debtAmount Amount of debt token to buy
* @param _debtToken Address of debt token
* @param _collateralAmount Amount of collateral token available to spend / used as maxAmountIn parameter
* @param _collateralToken Address of collateral token
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token spent
*/
function _swapCollateralForDebtToken(
uint256 _debtAmount,
address _debtToken,
uint256 _collateralAmount,
address _collateralToken,
DEXAdapterV4.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _debtToken)
returns (uint256)
{
return addresses.swapTokensForExactTokens(
_debtAmount,
_collateralAmount,
_swapData
);
}
/**
* Acquires the required amount of collateral tokens by swapping the input tokens
* Does nothing if collateral and input token are indentical
*
* @param _collateralToken Address of collateral token
* @param _amountRequired Remaining amount of collateral token required to repay flashloan, after having swapped debt tokens for collateral
* @param _inputToken Address of input token to swap
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of input token spent
*/
function _swapInputForCollateralToken(
address _collateralToken,
uint256 _amountRequired,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV4.SwapData memory _swapData
)
internal
isValidPath(
_swapData.path,
_inputToken,
_collateralToken
)
returns (uint256)
{
if(_collateralToken == _inputToken) return _amountRequired;
return addresses.swapTokensForExactTokens(
_amountRequired,
_maxAmountInputToken,
_swapData
);
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Address of collateral token
* @param _collateralTokenAmount Amount of colalteral token to swap
* @param _outputToken Address of the ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV4.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Deposit collateral to aave to obtain collateralAToken for issuance
*
* @param _collateralToken Address of collateral token
* @param _depositAmount Amount to deposit
*/
function _depositCollateralToken(
address _collateralToken,
uint256 _depositAmount
) internal {
LENDING_POOL.deposit(_collateralToken, _depositAmount, address(this), 0);
}
/**
* Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
*
* @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
* @param _collateralAmount Amount of collateralToken to withdraw
*/
function _withdrawCollateralToken(
address _collateralToken,
uint256 _collateralAmount
) internal {
LENDING_POOL.withdraw(_collateralToken, _collateralAmount, address(this));
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Approves max amount of token to lending pool
*
* @param _token Address of the token to approve
*/
function _approveTokenToLendingPool(
IERC20 _token
)
internal
{
uint256 allowance = _token.allowance(address(this), address(LENDING_POOL));
if (allowance > 0) {
_token.approve(address(LENDING_POOL), 0);
}
_token.approve(address(LENDING_POOL), MAX_UINT256);
}
/**
* Triggers the flashloan from the BalancerV2 Vault
*
* @param assets Addresses of tokens to loan
* @param amounts Amounts to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address[] memory assets,
uint256[] memory amounts,
bytes memory params
)
internal
{
require(flashLoanBenefactor == address(0), "Flashloan already taken");
flashLoanBenefactor = msg.sender;
balancerV2Vault.flashLoan(this, assets, amounts, params);
flashLoanBenefactor = address(0);
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amoun...
// [truncated — 50116 bytes total]
FlashMintLeveragedExtended.sol 705 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { FlashMintLeveraged } from "./FlashMintLeveraged.sol";
import { DEXAdapter } from "./DEXAdapter.sol";
import { IController } from "../interfaces/IController.sol";
import { IAaveLeverageModule } from "../interfaces/IAaveLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
/**
* @title FlashMintLeveragedExtended
* @author Index Coop
*
* Extended version of FlashMintLeveraged which allows for exactInputIssuance and exactOutputRedemption
*/
contract FlashMintLeveragedExtended is FlashMintLeveraged, Ownable {
uint256 public maxIterations = 10;
uint256 public maxGasRebate = 0.01 ether;
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _addresses dex adapter addreses
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _aaveV3Pool Address of address provider for aaves addresses
* @param _vault Balancer Vault to flashloan from
*/
constructor(
DEXAdapter.Addresses memory _addresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IAaveLeverageModule _aaveLeverageModule,
address _aaveV3Pool,
address _vault
) Ownable()
public
FlashMintLeveraged(_addresses, _setController, _debtIssuanceModule, _aaveLeverageModule, _aaveV3Pool, _vault)
{
}
/**
* Redeems a variable amount of setTokens to return exactly the specified amount of outputToken to the user
*
* @param _setToken Set token to redeem
* @param _maxSetAmount Maximum amout of set tokens to redeem
* @param _outputToken Address of output token to return to the user
* @param _outputTokenAmount Amount of output token to return to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataCollateralForOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output TOken
* @param _swapDataDebtForCollateral Data (token path and fee levels) describing the swap from Debt Token to Collateral Token
* @param _swapDataOutputTokenForCollateral Data (token path and fee levels) describing the swap from Output Token to Collateral Token
* @param _swapDataOutputTokenForETH Data (token path and fee levels) describing the swap from Output Token to ETH
* @param _priceEstimateInflator Factor by which to increase the estimated price from the previous iteration to account for used up liquidity
* @param _maxDust Minimum accuracy for approximating the output token amount. Excess will be swapped to eth and returned to user as gas rebate
*/
function redeemSetForExactERC20(
ISetToken _setToken,
uint256 _maxSetAmount,
address _outputToken,
uint256 _outputTokenAmount,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataCollateralForOutputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataOutputTokenForCollateral,
DEXAdapter.SwapData memory _swapDataOutputTokenForETH,
uint256 _priceEstimateInflator,
uint256 _maxDust
)
external
nonReentrant
returns(uint256 setAmount)
{
uint256 setBalanceBefore = _setToken.balanceOf(msg.sender);
uint256 outputTokenBalanceBefore = IERC20(_outputToken).balanceOf(address(this));
_initiateRedemption(
_setToken,
_maxSetAmount,
_outputToken,
_outputTokenAmount,
_swapDataCollateralForDebt,
_swapDataCollateralForOutputToken
);
_issueSetFromExcessOutput(
_setToken,
_maxSetAmount,
_outputToken,
_outputTokenAmount,
_swapDataDebtForCollateral,
_swapDataOutputTokenForCollateral,
_priceEstimateInflator,
_maxDust,
outputTokenBalanceBefore
);
_sendOutputTokenAndETHToUser(_outputToken, outputTokenBalanceBefore, _outputTokenAmount, _swapDataOutputTokenForETH);
return setBalanceBefore.sub(_setToken.balanceOf(msg.sender));
}
/**
* Redeems a variable amount of setTokens to return exactly the specified amount of ETH to the user
*
* @param _setToken Set token to redeem
* @param _maxSetAmount Maximum amout of set tokens to redeem
* @param _outputTokenAmount Amount of eth to return to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataCollateralForOutputToken Data (token path and fee levels) describing the swap from Collateral Token to eth
* @param _swapDataDebtForCollateral Data (token path and fee levels) describing the swap from Debt Token to Collateral Token
* @param _swapDataOutputTokenForCollateral Data (token path and fee levels) describing the swap from eth to Collateral Token
* @param _priceEstimateInflator Factor by which to increase the estimated price from the previous iteration to account for used up liquidity
* @param _maxDust Minimum accuracy for approximating the eth amount. Excess will be swapped to eth and returned to user as gas rebate
*/
function redeemSetForExactETH(
ISetToken _setToken,
uint256 _maxSetAmount,
uint256 _outputTokenAmount,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataCollateralForOutputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataOutputTokenForCollateral,
uint256 _priceEstimateInflator,
uint256 _maxDust
)
external
nonReentrant
returns(uint256)
{
uint256 wethBalanceBefore = IERC20(addresses.weth).balanceOf(address(this));
uint256 setBalanceBefore = _setToken.balanceOf(msg.sender);
_initiateRedemption(
_setToken,
_maxSetAmount,
DEXAdapter.ETH_ADDRESS,
_outputTokenAmount,
_swapDataCollateralForDebt,
_swapDataCollateralForOutputToken
);
_issueSetFromExcessOutput(
_setToken,
_maxSetAmount,
DEXAdapter.ETH_ADDRESS,
_outputTokenAmount,
_swapDataDebtForCollateral,
_swapDataOutputTokenForCollateral,
_priceEstimateInflator,
_maxDust,
wethBalanceBefore
);
uint256 wethObtained = IERC20(addresses.weth).balanceOf(address(this)).sub(wethBalanceBefore);
require(wethObtained >= _outputTokenAmount, "IWO");
require(wethObtained - _outputTokenAmount <= maxGasRebate, "MGR");
IWETH(addresses.weth).withdraw(wethObtained);
(payable(msg.sender)).sendValue(wethObtained);
return setBalanceBefore.sub(_setToken.balanceOf(msg.sender));
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
override
nonReentrant
{
uint256 wethBalanceBefore = IWETH(addresses.weth).balanceOf(address(this));
_initiateRedemption(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
uint256 amountToReturn = IWETH(addresses.weth).balanceOf(address(this)).sub(wethBalanceBefore);
IWETH(addresses.weth).withdraw(amountToReturn);
(payable(msg.sender)).sendValue(amountToReturn);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
override
nonReentrant
{
uint256 outputTokenBalanceBefore = IERC20(_outputToken).balanceOf(address(this));
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
uint256 outputTokenAmount = IERC20(_outputToken).balanceOf(address(this)).sub(outputTokenBalanceBefore);
IERC20(_outputToken).transfer(msg.sender, outputTokenAmount);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
override
nonReentrant
{
uint256 inputTokenBalanceBefore = IERC20(_inputToken).balanceOf(address(this));
IERC20(_inputToken).transferFrom(msg.sender, address(this), _maxAmountInputToken);
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
uint256 amountToReturn = IERC20(_inputToken).balanceOf(address(this)).sub(inputTokenBalanceBefore);
IERC20(_inputToken).transfer(msg.sender, amountToReturn);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
override
payable
nonReentrant
{
uint256 inputTokenBalanceBefore = IERC20(addresses.weth).balanceOf(address(this));
IWETH(addresses.weth).deposit{value: msg.value}();
_initiateIssuance(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
uint256 amountToReturn = IERC20(addresses.weth).balanceOf(address(this)).sub(inputTokenBalanceBefore);
IWETH(addresses.weth).withdraw(amountToReturn);
msg.sender.transfer(amountToReturn);
}
/**
* Issues a variable amount of set tokens for a fixed amount of input tokenss
*
* @param _setToken Set token to redeem
* @param _minSetAmount Minimum amount of Set Tokens to issue
* @param _inputToken Address of input token for which to issue set tokens
* @param _inputTokenAmount Amount of inputToken to return to the user
* @param _swapDataDebtForCollateral Data (token path and fee levels) describing the swap from Debt Token to Collateral Token
* @param _swapDataInputTokenForCollateral Data (token path and fee levels) describing the swap from input token to Collateral Token
* @param _swapDataInputTokenForETH Data (token path and fee levels) describing the swap from unspent input token to ETH, to use as gas rebate
* @param _priceEstimateInflator Factor by which to increase the estimated price from the previous iteration to account for used up liquidity
* @param _maxDust Minimum accuracy for approximating the input token amount. Excess will be swapped to input token and returned to user as gas rebate
*/
function issueSetFromExactERC20(
ISetToken _setToken,
uint256 _minSetAmount,
address _inputToken,
uint256 _inputTokenAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputTokenForCollateral,
DEXAdapter.SwapData memory _swapDataInputTokenForETH,
uint256 _priceEstimateInflator,
uint256 _maxDust
)
external
nonReentrant
returns(uint256)
{
uint256 setBalanceBefore = _setToken.balanceOf(msg.sender);
IERC20(_inputToken).transferFrom(msg.sender, address(this), _inputTokenAmount);
uint256 inputAmountLeft = _issueSetFromExactInput(
_setToken,
_minSetAmount,
_inputToken,
_inputTokenAmount,
_swapDataDebtForCollateral,
_swapDataInputTokenForCollateral,
_priceEstimateInflator,
_maxDust
);
_swapTokenForETHAndReturnToUser(_inputToken, inputAmountLeft, _swapDataInputTokenForETH);
return _setToken.balanceOf(msg.sender).sub(setBalanceBefore);
}
/**
* Issues a variable amount of set tokens for a fixed amount of eth
*
* @param _setToken Set token to redeem
* @param _minSetAmount Minimum amount of Set Tokens to issue
* @param _swapDataDebtForCollateral Data (token path and fee levels) describing the swap from Debt Token to Collateral Token
* @param _swapDataInputTokenForCollateral Data (token path and fee levels) describing the swap from eth to Collateral Token
* @param _priceEstimateInflator Factor by which to increase the estimated price from the previous iteration to account for used up liquidity
* @param _maxDust Minimum accuracy for approximating the eth amount. Excess will be swapped to eth and returned to user as gas rebate
*/
function issueSetFromExactETH(
ISetToken _setToken,
uint256 _minSetAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputTokenForCollateral,
uint256 _priceEstimateInflator,
uint256 _maxDust
)
external
payable
nonReentrant
returns(uint256)
{
uint256 setBalanceBefore = _setToken.balanceOf(msg.sender);
IWETH(addresses.weth).deposit{value: msg.value}();
uint256 inputTokenLeft = _issueSetFromExactInput(
_setToken,
_minSetAmount,
DEXAdapter.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputTokenForCollateral,
_priceEstimateInflator,
_maxDust
);
IWETH(addresses.weth).withdraw(inputTokenLeft);
msg.sender.transfer(inputTokenLeft);
return _setToken.balanceOf(msg.sender).sub(setBalanceBefore);
}
/**
* Update maximum number of iterations allowed in the fixed input issuance / fixed output redemption
*
* @param _maxIterations New value to set for maximum number of iterations. If "maxDust" is not met by that iteration the respective transaction will fail
*/
function setMaxIterations(uint256 _maxIterations) external onlyOwner {
maxIterations = _maxIterations;
}
/**
* Update maximum value of gas rebate returned to user in fixed input issuance / fixed output redemption
*
* @param _maxGasRebate New value to set for max gas rebate. Gas rebate above this value is assumed to be a misconfiguration and the respective transaction will fail
*/
function setMaxGasRebate(uint256 _maxGasRebate) external onlyOwner {
maxGasRebate = _maxGasRebate;
}
/* ============ Internal Functions ============ */
// @dev Use excess amout of output token to re-issue set tokens
function _issueSetFromExcessOutput(
ISetToken _setToken,
uint256 _maxSetAmount,
address _outputToken,
uint256 _outputTokenAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken,
uint256 _priceEstimateInflator,
uint256 _maxDust,
uint256 _outputTokenBalanceBefore
)
internal
{
uint256 obtainedOutputAmount;
if( _outputToken == DEXAdapter.ETH_ADDRESS) {
obtainedOutputAmount = IERC20(addresses.weth).balanceOf(address(this)).sub(_outputTokenBalanceBefore);
} else {
obtainedOutputAmount = IERC20(_outputToken).balanceOf(address(this)).sub(_outputTokenBalanceBefore);
}
uint256 excessOutputTokenAmount = obtainedOutputAmount.sub(_outputTokenAmount);
uint256 priceEstimate = _maxSetAmount.mul(_priceEstimateInflator).div(obtainedOutputAmount);
uint256 minSetAmount = excessOutputTokenAmount.mul(priceEstimate).div(1 ether);
_issueSetFromExactInput(
_setToken,
minSetAmount,
_outputToken,
excessOutputTokenAmount,
_swapDataDebtForCollateral,
_swapDataInputToken,
_priceEstimateInflator,
_maxDust
);
}
// @dev Send requested amount of Output tokens to user, sell excess for eth and send to user as gas rebate
function _sendOutputTokenAndETHToUser(
address _outputToken,
uint256 _outputTokenBalanceBefore,
uint256 _outputTokenAmount,
DEXAdapter.SwapData memory _swapDataOutputTokenForETH
)
internal
{
uint256 outputTokenObtained = IERC20(_outputToken).balanceOf(address(this)).sub(_outputTokenBalanceBefore);
require(outputTokenObtained >= _outputTokenAmount, "IOTO");
IERC20(_outputToken).transfer(msg.sender, _outputTokenAmount);
_swapTokenForETHAndReturnToUser(_outputToken, outputTokenObtained - _outputTokenAmount, _swapDataOutputTokenForETH);
}
// @dev Issue Set Tokens for (approximately) the requested amount of input tokens. Works by first issuing minimum requested amount of set tokens and then iteratively using observed exchange rate on previous issuance to spend the remaining input tokens until the difference is les than the specivied _maxDust
function _issueSetFromExactInput(
ISetToken _setToken,
uint256 _minSetAmount,
address _inputToken,
uint256 _inputTokenAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken,
uint256 _priceEstimateInflator,
uint256 _maxDust
)
internal
returns(uint256)
{
require(_inputTokenAmount > _maxDust, "MD");
uint256 iterations = 0;
while (_inputTokenAmount > _maxDust) {
require(iterations < maxIterations, "MI");
uint256 inputTokenAmountSpent = _initiateIssuanceAndReturnInputAmountSpent(
_setToken,
_minSetAmount,
_inputToken,
_inputTokenAmount,
_swapDataDebtForCollateral,
_swapDataInputToken
);
// Update remaining inputTokens left to be spent
_inputTokenAmount = _inputTokenAmount - inputTokenAmountSpent;
// Estimate price of setToken / inputToken, multiplying by provided factor to account for used up liquidity
uint256 priceEstimate = _minSetAmount.mul(_priceEstimateInflator).div(inputTokenAmountSpent);
// Amount to issue in next iteration is equal to the left over amount of input tokens times the price estimate from the previous step
_minSetAmount = _inputTokenAmount.mul(priceEstimate).div(1 ether);
iterations++;
}
return _inputTokenAmount;
}
// @dev Extends original _initiateIssuance by returning the amount of input tokens that was spent in issuance. (requisite for approximation algorithm above
function _initiateIssuanceAndReturnInputAmountSpent(
ISetToken _setToken,
uint256 _minSetAmount,
address _inputToken,
uint256 _inputTokenAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
internal
returns (uint256)
{
uint256 inputTokenBalanceBefore;
if( _inputToken == DEXAdapter.ETH_ADDRESS) {
inputTokenBalanceBefore = IERC20(addresses.weth).balanceOf(address(this));
} else {
inputTokenBalanceBefore = IERC20(_inputToken).balanceOf(address(this));
}
_initiateIssuance(
_setToken,
_minSetAmount,
_inputToken,
_inputTokenAmount,
_swapDataDebtForCollateral,
_swapDataInputToken
);
uint256 inputTokenBalanceAfter;
if( _inputToken == DEXAdapter.ETH_ADDRESS) {
inputTokenBalanceAfter = IERC20(addresses.weth).balanceOf(address(this));
} else {
inputTokenBalanceAfter = IERC20(_inputToken).balanceOf(address(this));
}
return inputTokenBalanceBefore.sub(inputTokenBalanceAfter);
}
/**
* @dev Same as in FlashMintLeveraged but without transfering input token from the user (since this is now done once at the very beginning to avoid transfering multiple times back and forth)
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
override
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
return _collateralTokenShortfall;
} else {
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
return amountInputToken;
}
}
/**
* @dev Same as in FlashMintLeveraged but without transfering eth from the user (since this is now done once at the very beginning to avoid transfering multiple times back and forth)
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapter.SwapData memory _swapData
)
internal
override
returns(uint256)
{
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
return amountEth;
}
/**
* @dev Same as in FlashMintLeveraged but without transfering output tokens to the user (since this is now done once at the very end to avoid transfering multiple times back and forth)
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
override
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
return outputTokenAmount;
}
/**
* @dev Same as in FlashMintLeveraged but without transfering eth to the user (since this is now done once at the very end to avoid transfering multiple times back and forth)
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
override
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
return ethAmount;
}
/**
* @dev Used to swap excess input / output tokens for eth and return to user as gas rebate
*/
function _swapTokenForETHAndReturnToUser(
address _inputToken,
uint256 _inputAmount,
DEXAdapter.SwapData memory _swapData
)
internal
{
uint256 ethObtained;
if(_inputToken == addresses.weth) {
ethObtained = _inputAmount;
} else {
// Setting path to empty array means opting out of the gas rebate swap
if(_swapData.path.length == 0) {
return;
}
require(_swapData.path[0] == _inputToken, "ITNF");
require(_swapData.path[_swapData.path.length - 1] == addresses.weth, "FlashMintLeveragedExtended: WETH not last in path");
ethObtained = addresses.swapExactTokensForTokens(
_inputAmount,
0,
_swapData
);
}
require(ethObtained <= maxGasRebate, "MGR");
IWETH(addresses.weth).withdraw(ethObtained);
msg.sender.transfer(ethObtained);
}
}
FlashMintLeveragedForCompound.sol 1299 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { ICErc20 } from "../interfaces/ICErc20.sol";
import { ICompoundLeverageModule } from "../interfaces/ICompoundLeverageModule.sol";
import { ICErc20Delegator } from "../interfaces/ICErc20Delegator.sol";
import { ICEther } from "../interfaces/ICEther.sol";
import { CErc20Storage } from "../interfaces/CErc20Storage.sol";
import { CompoundLeverageModuleStorage } from "../interfaces/CompoundLeverageModuleStorage.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
import { FlashLoanReceiverBaseV2 } from "../../external/contracts/aaveV2/FlashLoanReceiverBaseV2.sol";
import { DEXAdapter } from "./DEXAdapter.sol";
import { Exponential } from "../lib/Exponential.sol";
/**
* @title FlashMintLeveragedForCompound
* @author Index Coop
*
* Contract for minting and redeeming a leveraged Set token.
* Supports all tokens with one collateral Position in the form of a cToken and one debt position
* The collateral underlying must be available on an Aave flashloan.
* The collateral and debt tokens must be available on Compound.
* Input/Output tokens must be tradeable on supported dexes.
*/
contract FlashMintLeveragedForCompound is Exponential, ReentrancyGuard, FlashLoanReceiverBaseV2 {
using DEXAdapter for DEXAdapter.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralCToken;
uint256 cTokenAmount;
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapter.SwapData collateralAndDebtSwapData;
DEXAdapter.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ Immutables ============ */
address public immutable cEtherAddress;
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
ICompoundLeverageModule public immutable compoundLeverageModule;
/* ============ State Variables ============ */
DEXAdapter.Addresses public addresses;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the minted Set token
ISetToken indexed _setToken, // The minted Set token
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to mint the Set tokens
uint256 _amountInputToken, // The amount of input tokens used for minting
uint256 _amountSetIssued // The amount of Set tokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the Set token
ISetToken indexed _setToken, // The redeemed Set token
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of Set token redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier onlyLendingPool() {
require(msg.sender == address(LENDING_POOL), "FlashMint: LENDING POOL ONLY");
_;
}
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapter.ETH_ADDRESS),
"FlashMint: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapter.ETH_ADDRESS),
"FlashMint: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _dexAddresses Address of quickRouter, sushiRouter, uniV3Router, uniV3Router, curveAddressProvider, curveCalculator and weth.
* @param _setController Set token controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _compoundLeverageModule CompoundLeverageModule to sync before every mint / redemption
* @param _aaveAddressProvider Address of address provider for aaves addresses
* @param _cEther Address of Compound's cEther token
*/
constructor(
DEXAdapter.Addresses memory _dexAddresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
ICompoundLeverageModule _compoundLeverageModule,
address _aaveAddressProvider,
address _cEther
)
public
FlashLoanReceiverBaseV2(_aaveAddressProvider)
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
compoundLeverageModule = _compoundLeverageModule;
addresses = _dexAddresses;
cEtherAddress = _cEther;
}
/* ============ External Functions ============ */
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index.
*
* @param _setToken Address of the Set token to be minted / redeemed
* @param _setAmount Amount to mint / redeem
* @param _isMint Boolean indicating if the Set token is to be issued/minted or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isMint
)
external
view
returns (LeveragedTokenData memory)
{
return _getLeveragedTokenData(_setToken, _setAmount, _isMint);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a Set token during a rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the input cost of issuing/minting a given amount of a Set token. This
* function is not marked view, but should be static called off-chain.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on CompoundLeverageModule.
* @dev If the two SwapData paths contain the same tokens, there will be a slight error introduced in the result.
*
* @param _setToken Set token to mint
* @param _setAmount Amount to mint
* @param _swapDataDebtForCollateral SwapData (token addresses and fee levels) to describe the swap path from debt to collateral token
* @param _swapDataInputToken SwapData (token addresses and fee levels) to describe the swap path from input to collateral token
*
* @return the amount of input tokens required to perfrom the issuance
*/
function getIssueExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
returns (uint256)
{
compoundLeverageModule.sync(_setToken, false);
LeveragedTokenData memory issueInfo = _updateCompoundRateAndGetLeveragedTokenData(_setToken, _setAmount, true);
uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
uint256 borrowSaleProceeds = DEXAdapter.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
collateralOwed = collateralOwed.sub(borrowSaleProceeds);
return DEXAdapter.getAmountIn(addresses, _swapDataInputToken, collateralOwed);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on CompoundLeverageModule.
* @dev If the two SwapData paths contain the same tokens, there will be a slight error introduced in the result.
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _swapDataCollateralForDebt SwapData (token path and fee levels) describing the swap from collateral to debt token
* @param _swapDataOutputToken SwapData (token path and fee levels) describing the swap from collateral to output token
*
* @return amount of output token that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
compoundLeverageModule.sync(_setToken, true);
LeveragedTokenData memory redeemInfo = _updateCompoundRateAndGetLeveragedTokenData(_setToken, _setAmount, false);
uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
uint256 debtPurchaseCost = DEXAdapter.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed);
uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
return DEXAdapter.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
}
/**
* Trigger redemption of Set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt SwapData (token path and fee levels) describing the swap from collateral token to debt token
* @param _swapDataOutputToken SwapData (token path and fee levels) describing the swap from collateral token to output token
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
nonReentrant
{
_flashRedeem(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger redemption of Set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt SwapData (token path and fee levels) describing the swap from collateral token to debt token
* @param _swapDataOutputToken SwapData (token path and fee levels) describing the swap from collateral token to output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
external
nonReentrant
{
_flashRedeem(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger minting of Set token paying with any arbitrary ERC20 token.
*
* @param _setToken Set token to mint
* @param _setAmount Amount to mint
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral SwapData (token addresses and fee levels) to describe the swap path from debt to collateral token
* @param _swapDataInputToken SwapData (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
nonReentrant
{
_flashMint(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Trigger minting of set token paying with ETH.
*
* @param _setToken Set token to mint
* @param _setAmount Amount to mint
* @param _swapDataDebtForCollateral SwapData (token addresses and fee levels) to describe the swap path from debt to collateral token
* @param _swapDataInputToken SwapData (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
external
payable
nonReentrant
{
_flashMint(
_setToken,
_setAmount,
DEXAdapter.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* This is the callback function that will be called by the AaveLending Pool after flashloaned tokens have been sent
* to this contract.
* After exiting this function the Lending Pool will attempt to transfer back the loaned tokens + interest. If it fails to do so
* the whole transaction gets reverted
*
* @param assets Addresses of all assets that were borrowed
* @param amounts Amounts that were borrowed
* @param premiums Interest to be paid on top of borrowed amount
* @param initiator Address that initiated the flashloan
* @param params Encoded bytestring of other parameters from the original contract call to be used downstream
*
* @return Boolean indicating success of the operation (fixed to true otherwise the whole transaction would be reverted by lending pool)
*/
function executeOperation(
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
address initiator,
bytes memory params
)
external
override
onlyLendingPool
returns (bool)
{
require(initiator == address(this), "FlashMint: INVALID FLASHLOAN INITIATOR");
// assets.length must be 1.
require(assets.length == 1, "FlashMint: TOO MANY ASSETS");
require(amounts.length == 1, "FlashMint: TOO MANY AMOUNTS");
require(premiums.length == 1, "FlashMint: TOO MANY PREMIUMS");
DecodedParams memory decodedParams = abi.decode(params, (DecodedParams));
if(decodedParams.isIssuance){
_performMint(assets[0], amounts[0], premiums[0], decodedParams);
} else {
_performRedemption(assets[0], amounts[0], premiums[0], decodedParams);
}
return true;
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before minting or redeeming a Set token.
* This function need to be called only once before the first time this smart contract is used
* on any particular Set token.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
LeveragedTokenData memory leveragedTokenData = _updateCompoundRateAndGetLeveragedTokenData(_setToken, 1 ether, true);
_approveToken(IERC20(leveragedTokenData.collateralCToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
}
/* ============ Internal Functions ============ */
/**
* Performs all the necessary steps for minting using the collateral tokens obtained in the flashloan.
*
* @param _collateralToken Address of the underlying collateral token that was loaned
* @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform mint
*/
function _performMint(
address _collateralToken,
uint256 _collateralTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Deposit collateral token obtained from flashloan to get the respective cToken position required for issuance
_depositToCompound(_decodedParams.leveragedTokenData.collateralCToken, _collateralToken, _collateralTokenAmountNet);
// Issue set using the cToken returned by deposit step
_mintSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
uint amountInputTokenSpent = _obtainCollateralTokens(
_collateralToken,
_collateralTokenAmountNet + _premium,
_decodedParams
);
require(amountInputTokenSpent <= _decodedParams.limitAmount, "FlashMint: INSUFFICIENT INPUT AMOUNT");
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _debtToken Address of the debt token that was loaned
* @param _debtTokenAmountNet Amount of debt token that was received as flashloan
* @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
address _debtToken,
uint256 _debtTokenAmountNet,
uint256 _premium,
DecodedParams memory _decodedParams
)
internal
{
// Redeem set using debt tokens obtained from flashloan
_redeemSet(
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender
);
_withdrawFromCompound(
_decodedParams.leveragedTokenData.collateralCToken,
_decodedParams.leveragedTokenData.cTokenAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount
);
// Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
uint256 collateralTokenSpent = _swapCollateralForDebtToken(
_debtTokenAmountNet + _premium,
_debtToken,
_decodedParams.leveragedTokenData.collateralAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Liquidate remaining collateral tokens for the payment token specified by user
uint256 amountOutputToken = _liquidateCollateralTokens(
collateralTokenSpent,
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender,
_decodedParams.paymentToken,
_decodedParams.limitAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
_decodedParams.paymentTokenSwapData
);
require(amountOutputToken >= _decodedParams.limitAmount, "FlashMint: INSUFFICIENT OUTPUT AMOUNT");
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isMint Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getBasicLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isMint
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
LeveragedTokenData memory _leveragedTokenData;
if(_isMint){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(equityPositions[0] == 0 || equityPositions[1] == 0, "FlashMint: TOO MANY EQUITY POSITIONS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "FlashMint: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
_leveragedTokenData.collateralCToken = components[0];
_leveragedTokenData.cTokenAmount = equityPositions[0];
_leveragedTokenData.debtToken = components[1];
_leveragedTokenData.debtAmount = debtPositions[1];
} else {
_leveragedTokenData.collateralCToken = components[1];
_leveragedTokenData.cTokenAmount = equityPositions[1];
_leveragedTokenData.debtToken = components[0];
_leveragedTokenData.debtAmount = debtPositions[0];
}
if (_leveragedTokenData.collateralCToken == cEtherAddress) {
_leveragedTokenData.collateralToken = addresses.weth;
} else {
_leveragedTokenData.collateralToken = CErc20Storage(_leveragedTokenData.collateralCToken).underlying();
}
return _leveragedTokenData;
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the Set token to be minted / redeemed
* @param _setAmount Amount to mint / redeem
* @param _isMint Boolean indicating if the Set token is to be issued/minted or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isMint
)
internal
view
returns (LeveragedTokenData memory)
{
LeveragedTokenData memory _leveragedTokenData = _getBasicLeveragedTokenData(_setToken, _setAmount, _isMint);
Exp memory exchangeRate = Exp({mantissa: ICEther(payable(_leveragedTokenData.collateralCToken)).exchangeRateStored()});
(, uint256 collateralAmount) = mulScalarTruncate(exchangeRate, _leveragedTokenData.cTokenAmount);
_leveragedTokenData.collateralAmount = collateralAmount + ROUNDING_ERROR_MARGIN;
return _leveragedTokenData;
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the Set token to be minted / redeemed
* @param _setAmount Amount to mint / redeem
* @param _isMint Boolean indicating if the Set token is to be issued/minted or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _updateCompoundRateAndGetLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isMint
)
internal
returns (LeveragedTokenData memory)
{
LeveragedTokenData memory _leveragedTokenData = _getBasicLeveragedTokenData(_setToken, _setAmount, _isMint);
Exp memory exchangeRate = Exp({mantissa: ICEther(payable(_leveragedTokenData.collateralCToken)).exchangeRateCurrent()});
(, uint256 collateralAmount) = mulScalarTruncate(exchangeRate, _leveragedTokenData.cTokenAmount);
_leveragedTokenData.collateralAmount = collateralAmount + ROUNDING_ERROR_MARGIN;
return _leveragedTokenData;
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_token.approve(address(debtIssuanceModule), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for minting Set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to control mint in the callback.
*
* @param _setToken Set token to mint
* @param _setAmount Amount to mint
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral SwapData (token addresses and fee levels) to describe the swap path from debt to collateral token
* @param _swapDataInputToken SwapData (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function _flashMint(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapDataDebtForCollateral,
DEXAdapter.SwapData memory _swapDataInputToken
)
internal
{
// need to check (true or false to issue)
compoundLeverageModule.sync(_setToken, true);
LeveragedTokenData memory leveragedTokenData = _updateCompoundRateAndGetLeveragedTokenData(_setToken, _setAmount, true);
address[] memory assets = new address[](1);
assets[0] = leveragedTokenData.collateralToken;
uint[] memory amounts = new uint[](1);
amounts[0] = leveragedTokenData.collateralAmount;
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(assets, amounts, params);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming Set tokens in the callback
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the output token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt SwapData (token path and fee levels) describing the swap from collateral token to debt token
* @param _swapDataOutputToken SwapData (token path and fee levels) describing the swap from collateral token to output token
*/
function _flashRedeem(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapDataCollateralForDebt,
DEXAdapter.SwapData memory _swapDataOutputToken
)
internal
{
compoundLeverageModule.sync(_setToken, true);
LeveragedTokenData memory leveragedTokenData = _updateCompoundRateAndGetLeveragedTokenData(_setToken, _setAmount, false);
address[] memory assets = new address[](1);
assets[0] = leveragedTokenData.debtToken;
uint[] memory amounts = new uint[](1);
amounts[0] = leveragedTokenData.debtAmount;
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(assets, amounts, params);
}
/**
* Transfers output tokens to the user from redemption, if conversion is necessary,
* exchanges collateral token for output token and then transfers them out.
*
* @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _originalSender Account that initiated the redemption
* @param _outputToken Token to send to the user
* @param _collateralToken Collateral token to exchange for the output token
* @param _collateralAmount Amount to exchange
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapData SwapData (token path and fee levels) describing the swap from collateral token to output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapter.SwapData memory _swapData
)
internal
returns (uint256)
{
require(_collateralAmount >= _collateralTokenSpent, "FlashMint: OVERSPENT COLLATERAL TOKEN");
uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
uint256 outputAmount;
if(_outputToken == DEXAdapter.ETH_ADDRESS){
outputAmount = _liquidateCollateralTokensForETH(
_collateralToken,
amountToReturn,
_originalSender,
_minAmountOutputToken,
_swapData
);
} else {
outputAmount = _liquidateCollateralTokensForERC20(
_collateralToken,
amountToReturn,
_originalSender,
IERC20(_outputToken),
_minAmountOutputToken,
_swapData
);
}
emit FlashRedeem(_originalSender, _setToken, _outputToken, _setAmount, outputAmount);
return outputAmount;
}
/**
* Returns the collateral token directly to the user.
*
* @param _collateralToken Collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Original sender that is to receive the collateral token
*/
function _returnCollateralTokensToSender(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender
)
internal
{
IERC20(_collateralToken).transfer(_originalSender, _collateralRemaining);
}
/**
* Exchanges the collateral tokens for the output tokens and transfers them to the user.
*
* @param _collateralToken Collateral token
* @param _collateralRemaining Amount of the collateral tokens remaining after buying required debt tokens
* @param _originalSender Original sender that is to receive the output tokens
* @param _outputToken ERC20 token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapData SwapData (token path and fee levels) describing the swap from collateral token to output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
_returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
_outputToken.transfer(_originalSender, outputTokenAmount);
return outputTokenAmount;
}
/**
* Exchanges the remaining collateral tokens for weth, unwraps that weth and returns native eth to the user.
*
* @param _collateralToken Collateral token
* @param _collateralRemaining Amount of the collateral tokens remaining after buying required debt tokens
* @param _originalSender Original sender that is to receive the native eth
* @param _minAmountOutputToken Minimum amount of native eth to send to the user
* @param _swapData SwapData (token path and fee levels) describing the swap from collateral token to eth
*
* @return Amount of eth returned to the user
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
if (ethAmount > 0) {
IWETH(addresses.weth).withdraw(ethAmount);
(payable(_originalSender)).sendValue(ethAmount);
}
return ethAmount;
}
/**
* Obtains the collateral tokens necessary to return the flashloan by swapping the debt tokens obtained
* from mint and making up the shortfall using the users funds.
*
* @param _collateralToken Collateral token
* @param _amountRequired Amount required to repay the flashloan
* @param _decodedParams Struct containing decoded data from original call passed through via flashloan
*
* @return Amount of input token spent
*/
function _obtainCollateralTokens(
address _collateralToken,
uint256 _amountRequired,
DecodedParams memory _decodedParams
)
internal
returns (uint256)
{
uint collateralTokenObtained = _swapDebtForCollateralToken(
_collateralToken,
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.leveragedTokenData.debtAmount,
_decodedParams.collateralAndDebtSwapData
);
uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
uint amountInputToken;
if(_decodedParams.paymentToken == DEXAdapter.ETH_ADDRESS){
amountInputToken = _makeUpShortfallWithETH(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
} else {
amountInputToken = _makeUpShortfallWithERC20(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
IERC20(_decodedParams.paymentToken),
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
}
emit FlashMint(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountInputToken,
_decodedParams.setAmount
);
return amountInputToken;
}
/**
* Mints Set tokens using the previously obtained collateral token.
* Results in debt tokens being returned to the contract.
*
* @param _setToken Set token to mint
* @param _setAmount Amount to mint
* @param _originalSender Account that initiated the mint, which will receive the set tokens
*/
function _mintSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
}
/**
* Redeems Set tokens using the previously obtained debt token.
* Results in collateral tokens being returned to the contract.
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _originalSender Adress that initiated the redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
* from swapping the debt tokens from the users address.
*
* @param _token Set token to exchange shortfall
* @param _shortfall Amount of tokens that the tx is short
* @param _originalSender Account of originator, transfer the Set tokens from that account
*/
function _transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
internal
{
if(_shortfall>0){
IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
}
}
/**
* Makes up the collateral token shortfall with user specified ERC20 token.
*
* @param _collateralToken Collateral token
* @param _collateralTokenShortfall Amount of tokens that the tx is short after selling debt tokens
* @param _originalSender Originator account to return the tokens to
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
*
* @return Amount of input token spent
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
_transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
return _collateralTokenShortfall;
} else {
_inputToken.transferFrom(_originalSender, address(this), _maxAmountInputToken);
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
if(amountInputToken < _maxAmountInputToken){
_inputToken.transfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
}
return amountInputToken;
}
}
/**
* Makes up the collateral token shortfall with native eth.
*
* @param _collateralToken Collateral token
* @param _collateralTokenShortfall Amount of tokens that the tx is short after selling debt tokens
* @param _originalSender Originator account to return the tokens to
* @param _maxAmountEth Maximum amount of eth to pay
*
* @return Amount of eth spent
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapter.SwapData memory _swapData
)
internal
returns(uint256)
{
IWETH(addresses.weth).deposit{value: _maxAmountEth}();
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
if(_maxAmountEth > amountEth){
uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
IWETH(addresses.weth).withdraw(amountEthReturn);
(payable(_originalSender)).sendValue(amountEthReturn);
}
return amountEth;
}
/**
* Swaps the debt tokens obtained from minting for the collateral tokens.
*
* @param _collateralToken Collateral token to buy
* @param _debtToken Debt token to sell
* @param _debtAmount Amount of debt token to sell
* @param _swapData SwapData (token path and fee levels) describing the swap from debt token to collateral token
*
* @return Amount of collateral token obtained
*/
function _swapDebtForCollateralToken(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _debtToken, _collateralToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_debtAmount,
// minAmountOut is 0 here since we are going to make up the shortfall with the input token.
// Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
0,
_swapData
);
}
/**
* Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption.
*
* @param _debtAmount Amount of debt token to buy
* @param _debtToken Debt token
* @param _collateralAmount Amount of collateral token available (eg. maxAmountIn)
* @param _collateralToken Collateral token
* @param _swapData SwapData (token path and fee levels) describing the swap from collateral token to debt token
*
* @return Amount of collateral token spent
*/
function _swapCollateralForDebtToken(
uint256 _debtAmount,
address _debtToken,
uint256 _collateralAmount,
address _collateralToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _debtToken)
returns (uint256)
{
return addresses.swapTokensForExactTokens(
_debtAmount,
_collateralAmount,
_swapData
);
}
/**
* Acquires the required amount of collateral tokens by exchanging the input tokens.
* Does nothing if collateral and input token are indentical.
*
* @param _collateralToken Collateral token
* @param _amountRequired Amount required to repay the flashloan
* @param _inputToken Input token
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapData SwapData (token path and fee levels) describing the swap from input token to debt token
*
* @return Amount of input token spent
*/
function _swapInputForCollateralToken(
address _collateralToken,
uint256 _amountRequired,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(
_swapData.path,
_inputToken,
_collateralToken
)
returns (uint256)
{
if(_collateralToken == _inputToken) return _amountRequired;
return addresses.swapTokensForExactTokens(
_amountRequired,
_maxAmountInputToken,
_swapData
);
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Collateral token
* @param _collateralTokenAmount Amount to swap
* @param _outputToken ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapData SwapData (token path and fee levels) describing the swap from collateral token to output token
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Deposit collateral to compound to obtain collateralCToken for mint.
*
* @param _cTokenAddress cToken
* @param _collateralToken Collateral token
* @param _depositAmount Amount to deposit
*/
function _depositToCompound(
address _cTokenAddress,
address _collateralToken,
uint256 _depositAmount
) internal {
if (_collateralToken != addresses.weth) {
IERC20(_collateralToken).approve(_cTokenAddress, _depositAmount);
ICErc20Delegator(_cTokenAddress).mint(_depositAmount);
} else {
IWETH(addresses.weth).withdraw(_depositAmount);
ICEther(payable(...
// [truncated — 52248 bytes total]
FlashMintLeveragedMorpho.sol 1155 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IMorphoLeverageModule } from "../interfaces/IMorphoLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { DEXAdapterV5 } from "./DEXAdapterV5.sol";
import { IMorpho } from "../interfaces/IMorpho.sol";
/**
* @title FlashMintLeveraged
* @author Index Coop
*
* Contract for issuing and redeeming a leveraged Set Token
* Supports all tokens with one morpho collateral Position and one debt position
* Both the collateral as well as the debt token have to be available for flashloan from morpho and be
* tradeable against each other on Sushi / Quickswap
* Uses DexAdapterV5 for Aerodrome Support
*/
contract FlashMintLeveragedMorpho is ReentrancyGuard {
using DEXAdapterV5 for DEXAdapterV5.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapterV5.SwapData collateralAndDebtSwapData;
DEXAdapterV5.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IMorphoLeverageModule public immutable morphoLeverageModule;
IMorpho public immutable morpho;
DEXAdapterV5.Addresses public addresses;
address private flashLoanBenefactor;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapterV5.ETH_ADDRESS),
"ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapterV5.ETH_ADDRESS),
"ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _addresses dex adapter addreses
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _morphoLeverageModule MorphoLeverageModule to sync before every issuance / redemption
* @param _morpho Morpho contract to call for flashloan
*/
constructor(
DEXAdapterV5.Addresses memory _addresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IMorphoLeverageModule _morphoLeverageModule,
IMorpho _morpho
)
public
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
morphoLeverageModule = _morphoLeverageModule;
addresses = _addresses;
morpho = _morpho;
}
/* ============ External Functions ============ */
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
external
view
returns (LeveragedTokenData memory)
{
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the input cost of issuing a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on MorphoLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _maxAmountInputToken maximum amount of input token to spend
* @param _swapDataDebtForCollateral swap data for the debt to collateral swap
* @param _swapDataInputToken swap data for the input token to collateral swap
*
* @return the amount of input tokens required to perfrom the issuance
*/
function getIssueExactSet(
ISetToken _setToken,
uint256 _setAmount,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
returns (uint256)
{
morphoLeverageModule.sync(_setToken);
LeveragedTokenData memory issueInfo = _getLeveragedTokenData(_setToken, _setAmount, true);
uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
uint256 borrowSaleProceeds = DEXAdapterV5.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
collateralOwed = collateralOwed.sub(borrowSaleProceeds);
return DEXAdapterV5.getAmountIn(addresses, _swapDataInputToken, collateralOwed, _maxAmountInputToken);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on MorphoLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataCollateralForDebt swap data for the collateral to debt swap
* @param _swapDataOutputToken swap data for the collateral token to the output token
*
* @return amount of _outputToken that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
morphoLeverageModule.sync(_setToken);
LeveragedTokenData memory redeemInfo = _getLeveragedTokenData(_setToken, _setAmount, false);
uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
uint256 debtPurchaseCost = DEXAdapterV5.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed, redeemInfo.collateralAmount);
uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
return DEXAdapterV5.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
DEXAdapterV5.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
virtual
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
virtual
payable
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
DEXAdapterV5.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Callback function called by Morpho after flashloan has been requested
*
* @param assets Amount of tokens loaned / to be repayed
* @param data Encoded data containing the original sender and leveraged token data
*/
function onMorphoFlashLoan(uint256 assets, bytes calldata data) external {
require(msg.sender == address(morpho));
DecodedParams memory decodedParams = abi.decode(data, (DecodedParams));
require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
if(decodedParams.isIssuance){
_performIssuance(decodedParams.leveragedTokenData.collateralToken, assets, decodedParams);
IERC20(decodedParams.leveragedTokenData.collateralToken).approve(address(morpho), assets);
} else {
_performRedemption(decodedParams.leveragedTokenData.debtToken, assets, decodedParams);
IERC20(decodedParams.leveragedTokenData.debtToken).approve(address(morpho), assets);
}
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true);
_approveToken(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
}
/* ============ Internal Functions ============ */
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _collateralToken Address of the underlying collateral token that was loaned
* @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
address _collateralToken,
uint256 _collateralTokenAmountNet,
DecodedParams memory _decodedParams
)
internal
{
_issueSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
uint amountInputTokenSpent = _obtainCollateralTokens(
_collateralToken,
_collateralTokenAmountNet,
_decodedParams
);
require(amountInputTokenSpent <= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT INPUT AMOUNT");
emit FlashMint(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountInputTokenSpent,
_decodedParams.setAmount
);
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _debtToken Address of the debt token that was loaned
* @param _debtTokenAmountNet Amount of debt token that was received as flashloan
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
address _debtToken,
uint256 _debtTokenAmountNet,
DecodedParams memory _decodedParams
)
internal
{
// Redeem set using debt tokens obtained from flashloan
_redeemSet(
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender
);
// Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
uint256 collateralTokenSpent = _swapCollateralForDebtToken(
_debtTokenAmountNet,
_debtToken,
_decodedParams.leveragedTokenData.collateralAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Liquidate remaining collateral tokens for the payment token specified by user
uint256 amountOutputToken = _liquidateCollateralTokens(
collateralTokenSpent,
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender,
_decodedParams.paymentToken,
_decodedParams.limitAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
_decodedParams.paymentTokenSwapData
);
require(amountOutputToken >= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
emit FlashRedeem(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountOutputToken,
_decodedParams.setAmount
);
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(equityPositions[0] == 0 || equityPositions[1] == 0, "ExchangeIssuance: TOO MANY EQUITY POSITIONS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
return LeveragedTokenData(
components[0],
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
components[1],
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
internal
{
morphoLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(leveragedTokenData.collateralToken, leveragedTokenData.collateralAmount, params);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
internal
{
morphoLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(leveragedTokenData.debtToken, leveragedTokenData.debtAmount, params);
}
/**
* Gets rid of the obtained collateral tokens from redemption by either sending them to the user
* directly or converting them to the payment token and sending those out.
*
* @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Address of the user who initiated the redemption
* @param _outputToken Address of token to return to the user
* @param _collateralToken Address of the collateral token to sell
* @param _collateralAmount Amount of collateral token to sell
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapterV5.SwapData memory _swapData
)
internal
returns (uint256)
{
require(_collateralAmount >= _collateralTokenSpent, "ExchangeIssuance: OVERSPENT COLLATERAL TOKEN");
uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
uint256 outputAmount;
if(_outputToken == DEXAdapterV5.ETH_ADDRESS){
outputAmount = _liquidateCollateralTokensForETH(
_collateralToken,
amountToReturn,
_originalSender,
_minAmountOutputToken,
_swapData
);
} else {
outputAmount = _liquidateCollateralTokensForERC20(
_collateralToken,
amountToReturn,
_originalSender,
IERC20(_outputToken),
_minAmountOutputToken,
_swapData
);
}
return outputAmount;
}
/**
* Returns the collateralToken directly to the user
*
* @param _collateralToken Address of the the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
*/
function _returnCollateralTokensToSender(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender
)
internal
{
IERC20(_collateralToken).transfer(_originalSender, _collateralRemaining);
}
/**
* Sells the collateral tokens for the selected output ERC20 and returns that to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to Output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
_returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
_outputToken.transfer(_originalSender, outputTokenAmount);
return outputTokenAmount;
}
/**
* Sells the remaining collateral tokens for weth, withdraws that and returns native eth to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the eth to
* @param _minAmountOutputToken Minimum amount of output token to return to user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to eth
*
* @return Amount of eth returned to the user
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
if (ethAmount > 0) {
IWETH(addresses.weth).withdraw(ethAmount);
(payable(_originalSender)).sendValue(ethAmount);
}
return ethAmount;
}
/**
* Obtains the tokens necessary to return the flashloan by swapping the debt tokens obtained
* from issuance and making up the shortfall using the users funds.
*
* @param _collateralToken collateral token to obtain
* @param _amountRequired Amount of collateralToken required to repay the flashloan
* @param _decodedParams Struct containing decoded data from original call passed through via flashloan
*
* @return Amount of input token spent
*/
function _obtainCollateralTokens(
address _collateralToken,
uint256 _amountRequired,
DecodedParams memory _decodedParams
)
internal
returns (uint256)
{
uint collateralTokenObtained = _swapDebtForCollateralToken(
_collateralToken,
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.leveragedTokenData.debtAmount,
_decodedParams.collateralAndDebtSwapData
);
uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
uint amountInputToken;
if(_decodedParams.paymentToken == DEXAdapterV5.ETH_ADDRESS){
amountInputToken = _makeUpShortfallWithETH(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
} else {
amountInputToken = _makeUpShortfallWithERC20(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
IERC20(_decodedParams.paymentToken),
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
}
return amountInputToken;
}
/**
* Issues set token using the previously obtained collateral token
* Results in debt token being returned to the contract
*
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Adress that initiated the token issuance, which will receive the set tokens
*/
function _issueSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
}
/**
* Redeems set token using the previously obtained debt token
* Results in collateral token being returned to the contract
*
* @param _setToken Address of the SetToken to be redeemed
* @param _setAmount Amount of SetTokens to redeem
* @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
* from swapping the debt tokens from the users address
*
* @param _token Address of the token to transfer from user
* @param _shortfall Collateral token shortfall required to return the flashloan
* @param _originalSender Adress that initiated the token issuance, which is the adresss form which to transfer the tokens
*/
function _transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
internal
{
if(_shortfall>0){
IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
}
}
/**
* Makes up the collateral token shortfall with user specified ERC20 token
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
*
* @return Amount of input token spent
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
_transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
return _collateralTokenShortfall;
} else {
_inputToken.transferFrom(_originalSender, address(this), _maxAmountInputToken);
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
if(amountInputToken < _maxAmountInputToken){
_inputToken.transfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
}
return amountInputToken;
}
}
/**
* Makes up the collateral token shortfall with native eth
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _maxAmountEth Maximum amount of eth to pay
*
* @return Amount of eth spent
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns(uint256)
{
IWETH(addresses.weth).deposit{value: _maxAmountEth}();
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
if(_maxAmountEth > amountEth){
uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
IWETH(addresses.weth).withdraw(amountEthReturn);
(payable(_originalSender)).sendValue(amountEthReturn);
}
return amountEth;
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _collateralToken Address of the collateral token buy
* @param _debtToken Address of the debt token to sell
* @param _debtAmount Amount of debt token to sell
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token obtained
*/
function _swapDebtForCollateralToken(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _debtToken, _collateralToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_debtAmount,
// minAmountOut is 0 here since we are going to make up the shortfall with the input token.
// Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
0,
_swapData
);
}
/**
* Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption
*
* @param _debtAmount Amount of debt token to buy
* @param _debtToken Address of debt token
* @param _collateralAmount Amount of collateral token available to spend / used as maxAmountIn parameter
* @param _collateralToken Address of collateral token
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token spent
*/
function _swapCollateralForDebtToken(
uint256 _debtAmount,
address _debtToken,
uint256 _collateralAmount,
address _collateralToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _debtToken)
returns (uint256)
{
return addresses.swapTokensForExactTokens(
_debtAmount,
_collateralAmount,
_swapData
);
}
/**
* Acquires the required amount of collateral tokens by swapping the input tokens
* Does nothing if collateral and input token are indentical
*
* @param _collateralToken Address of collateral token
* @param _amountRequired Remaining amount of collateral token required to repay flashloan, after having swapped debt tokens for collateral
* @param _inputToken Address of input token to swap
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of input token spent
*/
function _swapInputForCollateralToken(
address _collateralToken,
uint256 _amountRequired,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(
_swapData.path,
_inputToken,
_collateralToken
)
returns (uint256)
{
if(_collateralToken == _inputToken) return _amountRequired;
return addresses.swapTokensForExactTokens(
_amountRequired,
_maxAmountInputToken,
_swapData
);
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Address of collateral token
* @param _collateralTokenAmount Amount of colalteral token to swap
* @param _outputToken Address of the ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Triggers the flashloan from the BalancerV2 Vault
*
* @param token Address of the token to loan
* @param amount Amount to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address token,
uint256 amount,
bytes memory params
)
internal
{
require(flashLoanBenefactor == address(0), "Flashloan already taken");
flashLoanBenefactor = msg.sender;
morpho.flashLoan(token, amount, params);
flashLoanBenefactor = address(0);
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
debtIssuanceModule.redeem(_setToken, _amount, address(this));
}
receive() external payable {}
}
FlashMintLeveragedMorphoAaveLM.sol 1215 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import { IAaveLeverageModule } from "../interfaces/IAaveLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { DEXAdapterV5 } from "./DEXAdapterV5.sol";
import { IMorpho } from "../interfaces/IMorpho.sol";
import { IPool } from "../interfaces/IPool.sol";
/**
* @title FlashMintLeveragedMorphoAaveLm
* @author Index Coop
*
* Compatible with aave based leveraged index tokens
* Uses Morpho for flashloans
* Includes support for Aerodrome Slipstream
*/
contract FlashMintLeveragedMorphoAaveLM is ReentrancyGuard {
using DEXAdapterV5 for DEXAdapterV5.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralAToken;
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapterV5.SwapData collateralAndDebtSwapData;
DEXAdapterV5.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IAaveLeverageModule public immutable aaveLeverageModule;
IMorpho public immutable morpho;
IPool public immutable LENDING_POOL;
DEXAdapterV5.Addresses public addresses;
address private flashLoanBenefactor;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapterV5.ETH_ADDRESS),
"ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapterV5.ETH_ADDRESS),
"ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _addresses dex adapter addreses
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _morpho Morpho contract to call for flashloan
*/
constructor(
DEXAdapterV5.Addresses memory _addresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IAaveLeverageModule _aaveLeverageModule,
IMorpho _morpho,
IPool _lendingPool
)
public
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
aaveLeverageModule = _aaveLeverageModule;
addresses = _addresses;
morpho = _morpho;
LENDING_POOL = _lendingPool;
}
/* ============ External Functions ============ */
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
external
view
returns (LeveragedTokenData memory)
{
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the input cost of issuing a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _maxAmountInputToken maximum amount of input token to spend
* @param _swapDataDebtForCollateral swap data for the debt to collateral swap
* @param _swapDataInputToken swap data for the input token to collateral swap
*
* @return the amount of input tokens required to perfrom the issuance
*/
function getIssueExactSet(
ISetToken _setToken,
uint256 _setAmount,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory issueInfo = _getLeveragedTokenData(_setToken, _setAmount, true);
uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
uint256 borrowSaleProceeds = DEXAdapterV5.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
collateralOwed = collateralOwed.sub(borrowSaleProceeds);
return DEXAdapterV5.getAmountIn(addresses, _swapDataInputToken, collateralOwed, _maxAmountInputToken);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on AaveLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataCollateralForDebt swap data for the collateral to debt swap
* @param _swapDataOutputToken swap data for the collateral token to the output token
*
* @return amount of _outputToken that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory redeemInfo = _getLeveragedTokenData(_setToken, _setAmount, false);
uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
uint256 debtPurchaseCost = DEXAdapterV5.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed, redeemInfo.collateralAmount);
uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
return DEXAdapterV5.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
DEXAdapterV5.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
virtual
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
virtual
payable
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
DEXAdapterV5.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Callback function called by Morpho after flashloan has been requested
*
* @param assets Amount of tokens loaned / to be repayed
* @param data Encoded data containing the original sender and leveraged token data
*/
function onMorphoFlashLoan(uint256 assets, bytes calldata data) external {
require(msg.sender == address(morpho));
DecodedParams memory decodedParams = abi.decode(data, (DecodedParams));
require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
if(decodedParams.isIssuance){
_performIssuance(decodedParams.leveragedTokenData.collateralToken, assets, decodedParams);
IERC20(decodedParams.leveragedTokenData.collateralToken).approve(address(morpho), assets);
} else {
_performRedemption(decodedParams.leveragedTokenData.debtToken, assets, decodedParams);
IERC20(decodedParams.leveragedTokenData.debtToken).approve(address(morpho), assets);
}
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true);
_approveToken(IERC20(leveragedTokenData.collateralAToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
}
/* ============ Internal Functions ============ */
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _collateralToken Address of the underlying collateral token that was loaned
* @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
address _collateralToken,
uint256 _collateralTokenAmountNet,
DecodedParams memory _decodedParams
)
internal
{
// Deposit collateral token obtained from flashloan to get the respective aToken position required for issuance
_depositCollateralToken(_collateralToken, _collateralTokenAmountNet);
_issueSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
uint amountInputTokenSpent = _obtainCollateralTokens(
_collateralToken,
_collateralTokenAmountNet,
_decodedParams
);
require(amountInputTokenSpent <= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT INPUT AMOUNT");
emit FlashMint(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountInputTokenSpent,
_decodedParams.setAmount
);
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _debtToken Address of the debt token that was loaned
* @param _debtTokenAmountNet Amount of debt token that was received as flashloan
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
address _debtToken,
uint256 _debtTokenAmountNet,
DecodedParams memory _decodedParams
)
internal
{
// Redeem set using debt tokens obtained from flashloan
_redeemSet(
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender
);
// Withdraw underlying collateral token from the aToken position returned by redeem step
_withdrawCollateralToken(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - ROUNDING_ERROR_MARGIN
);
// Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
uint256 collateralTokenSpent = _swapCollateralForDebtToken(
_debtTokenAmountNet,
_debtToken,
_decodedParams.leveragedTokenData.collateralAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Liquidate remaining collateral tokens for the payment token specified by user
uint256 amountOutputToken = _liquidateCollateralTokens(
collateralTokenSpent,
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender,
_decodedParams.paymentToken,
_decodedParams.limitAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
_decodedParams.paymentTokenSwapData
);
require(amountOutputToken >= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
emit FlashRedeem(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountOutputToken,
_decodedParams.setAmount
);
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(equityPositions[0] == 0 || equityPositions[1] == 0, "ExchangeIssuance: TOO MANY EQUITY POSITIONS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
return LeveragedTokenData(
components[0],
IAToken(components[0]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
components[1],
IAToken(components[1]).UNDERLYING_ASSET_ADDRESS(),
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(leveragedTokenData.collateralToken, leveragedTokenData.collateralAmount, params);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
internal
{
aaveLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(leveragedTokenData.debtToken, leveragedTokenData.debtAmount, params);
}
/**
* Gets rid of the obtained collateral tokens from redemption by either sending them to the user
* directly or converting them to the payment token and sending those out.
*
* @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Address of the user who initiated the redemption
* @param _outputToken Address of token to return to the user
* @param _collateralToken Address of the collateral token to sell
* @param _collateralAmount Amount of collateral token to sell
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapterV5.SwapData memory _swapData
)
internal
returns (uint256)
{
require(_collateralAmount >= _collateralTokenSpent, "ExchangeIssuance: OVERSPENT COLLATERAL TOKEN");
uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
uint256 outputAmount;
if(_outputToken == DEXAdapterV5.ETH_ADDRESS){
outputAmount = _liquidateCollateralTokensForETH(
_collateralToken,
amountToReturn,
_originalSender,
_minAmountOutputToken,
_swapData
);
} else {
outputAmount = _liquidateCollateralTokensForERC20(
_collateralToken,
amountToReturn,
_originalSender,
IERC20(_outputToken),
_minAmountOutputToken,
_swapData
);
}
return outputAmount;
}
/**
* Returns the collateralToken directly to the user
*
* @param _collateralToken Address of the the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
*/
function _returnCollateralTokensToSender(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender
)
internal
{
IERC20(_collateralToken).transfer(_originalSender, _collateralRemaining);
}
/**
* Sells the collateral tokens for the selected output ERC20 and returns that to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to Output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
_returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
_outputToken.transfer(_originalSender, outputTokenAmount);
return outputTokenAmount;
}
/**
* Sells the remaining collateral tokens for weth, withdraws that and returns native eth to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the eth to
* @param _minAmountOutputToken Minimum amount of output token to return to user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to eth
*
* @return Amount of eth returned to the user
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
if (ethAmount > 0) {
IWETH(addresses.weth).withdraw(ethAmount);
(payable(_originalSender)).sendValue(ethAmount);
}
return ethAmount;
}
/**
* Obtains the tokens necessary to return the flashloan by swapping the debt tokens obtained
* from issuance and making up the shortfall using the users funds.
*
* @param _collateralToken collateral token to obtain
* @param _amountRequired Amount of collateralToken required to repay the flashloan
* @param _decodedParams Struct containing decoded data from original call passed through via flashloan
*
* @return Amount of input token spent
*/
function _obtainCollateralTokens(
address _collateralToken,
uint256 _amountRequired,
DecodedParams memory _decodedParams
)
internal
returns (uint256)
{
uint collateralTokenObtained = _swapDebtForCollateralToken(
_collateralToken,
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.leveragedTokenData.debtAmount,
_decodedParams.collateralAndDebtSwapData
);
uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
uint amountInputToken;
if(_decodedParams.paymentToken == DEXAdapterV5.ETH_ADDRESS){
amountInputToken = _makeUpShortfallWithETH(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
} else {
amountInputToken = _makeUpShortfallWithERC20(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
IERC20(_decodedParams.paymentToken),
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
}
return amountInputToken;
}
/**
* Issues set token using the previously obtained collateral token
* Results in debt token being returned to the contract
*
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Adress that initiated the token issuance, which will receive the set tokens
*/
function _issueSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
}
/**
* Redeems set token using the previously obtained debt token
* Results in collateral token being returned to the contract
*
* @param _setToken Address of the SetToken to be redeemed
* @param _setAmount Amount of SetTokens to redeem
* @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
* from swapping the debt tokens from the users address
*
* @param _token Address of the token to transfer from user
* @param _shortfall Collateral token shortfall required to return the flashloan
* @param _originalSender Adress that initiated the token issuance, which is the adresss form which to transfer the tokens
*/
function _transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
internal
{
if(_shortfall>0){
IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
}
}
/**
* Makes up the collateral token shortfall with user specified ERC20 token
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
*
* @return Amount of input token spent
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
_transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
return _collateralTokenShortfall;
} else {
_inputToken.transferFrom(_originalSender, address(this), _maxAmountInputToken);
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
if(amountInputToken < _maxAmountInputToken){
_inputToken.transfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
}
return amountInputToken;
}
}
/**
* Makes up the collateral token shortfall with native eth
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _maxAmountEth Maximum amount of eth to pay
*
* @return Amount of eth spent
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns(uint256)
{
IWETH(addresses.weth).deposit{value: _maxAmountEth}();
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
if(_maxAmountEth > amountEth){
uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
IWETH(addresses.weth).withdraw(amountEthReturn);
(payable(_originalSender)).sendValue(amountEthReturn);
}
return amountEth;
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _collateralToken Address of the collateral token buy
* @param _debtToken Address of the debt token to sell
* @param _debtAmount Amount of debt token to sell
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token obtained
*/
function _swapDebtForCollateralToken(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _debtToken, _collateralToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_debtAmount,
// minAmountOut is 0 here since we are going to make up the shortfall with the input token.
// Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
0,
_swapData
);
}
/**
* Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption
*
* @param _debtAmount Amount of debt token to buy
* @param _debtToken Address of debt token
* @param _collateralAmount Amount of collateral token available to spend / used as maxAmountIn parameter
* @param _collateralToken Address of collateral token
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token spent
*/
function _swapCollateralForDebtToken(
uint256 _debtAmount,
address _debtToken,
uint256 _collateralAmount,
address _collateralToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _debtToken)
returns (uint256)
{
return addresses.swapTokensForExactTokens(
_debtAmount,
_collateralAmount,
_swapData
);
}
/**
* Acquires the required amount of collateral tokens by swapping the input tokens
* Does nothing if collateral and input token are indentical
*
* @param _collateralToken Address of collateral token
* @param _amountRequired Remaining amount of collateral token required to repay flashloan, after having swapped debt tokens for collateral
* @param _inputToken Address of input token to swap
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of input token spent
*/
function _swapInputForCollateralToken(
address _collateralToken,
uint256 _amountRequired,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(
_swapData.path,
_inputToken,
_collateralToken
)
returns (uint256)
{
if(_collateralToken == _inputToken) return _amountRequired;
return addresses.swapTokensForExactTokens(
_amountRequired,
_maxAmountInputToken,
_swapData
);
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Address of collateral token
* @param _collateralTokenAmount Amount of colalteral token to swap
* @param _outputToken Address of the ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Deposit collateral to aave to obtain collateralAToken for issuance
*
* @param _collateralToken Address of collateral token
* @param _depositAmount Amount to deposit
*/
function _depositCollateralToken(
address _collateralToken,
uint256 _depositAmount
) internal {
LENDING_POOL.deposit(_collateralToken, _depositAmount, address(this), 0);
}
/**
* Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
*
* @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
* @param _collateralAmount Amount of collateralToken to withdraw
*/
function _withdrawCollateralToken(
address _collateralToken,
uint256 _collateralAmount
) internal {
LENDING_POOL.withdraw(_collateralToken, _collateralAmount, address(this));
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Approves max amount of token to lending pool
*
* @param _token Address of the token to approve
*/
function _approveTokenToLendingPool(
IERC20 _token
)
internal
{
uint256 allowance = _token.allowance(address(this), address(LENDING_POOL));
if (allowance > 0) {
_token.approve(address(LENDING_POOL), 0);
}
_token.approve(address(LENDING_POOL), MAX_UINT256);
}
/**
* Triggers the flashloan from the BalancerV2 Vault
*
* @param token Address of the token to loan
* @param amount Amount to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address token,
uint256 amount,
bytes memory params
)
internal
{
require(flashLoanBenefactor == address(0), "Flashloan already taken");
flashLoanBenefactor = msg.sender;
morpho.flashLoan(token, amount, params);
flashLoanBenefactor = address(0);
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
debtIssuanceModule.redeem(_setToken, _amount, address(this));
}
receive() external payable {}
}
FlashMintLeveragedMorphoV2.sol 1155 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IMorphoLeverageModule } from "../interfaces/IMorphoLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { DEXAdapterV5 } from "./DEXAdapterV5.sol";
import { IMorpho } from "../interfaces/IMorpho.sol";
/**
* @title FlashMintLeveraged
* @author Index Coop
*
* Contract for issuing and redeeming a leveraged Set Token
* Supports all tokens with one morpho collateral Position and one debt position
* Both the collateral as well as the debt token have to be available for flashloan from morpho and be
* tradeable against each other on Sushi / Quickswap
* Uses DexAdapterV5 for Aerodrome Support
* Works for leverage tokens with debt token dust
*/
contract FlashMintLeveragedMorphoV2 is ReentrancyGuard {
using DEXAdapterV5 for DEXAdapterV5.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct DecodedParams {
ISetToken setToken;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
DEXAdapterV5.SwapData collateralAndDebtSwapData;
DEXAdapterV5.SwapData paymentTokenSwapData;
}
/* ============ Constants ============= */
uint256 constant private MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IMorphoLeverageModule public immutable morphoLeverageModule;
IMorpho public immutable morpho;
DEXAdapterV5.Addresses public addresses;
address private flashLoanBenefactor;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
)
{
if(_inputToken != _outputToken){
require(
_path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapterV5.ETH_ADDRESS),
"ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length-1] == _outputToken ||
(_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapterV5.ETH_ADDRESS),
"ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _addresses dex adapter addreses
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _morphoLeverageModule MorphoLeverageModule to sync before every issuance / redemption
* @param _morpho Morpho contract to call for flashloan
*/
constructor(
DEXAdapterV5.Addresses memory _addresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IMorphoLeverageModule _morphoLeverageModule,
IMorpho _morpho
)
public
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
morphoLeverageModule = _morphoLeverageModule;
addresses = _addresses;
morpho = _morpho;
}
/* ============ External Functions ============ */
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
external
view
returns (LeveragedTokenData memory)
{
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Gets the input cost of issuing a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on MorphoLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _maxAmountInputToken maximum amount of input token to spend
* @param _swapDataDebtForCollateral swap data for the debt to collateral swap
* @param _swapDataInputToken swap data for the input token to collateral swap
*
* @return the amount of input tokens required to perfrom the issuance
*/
function getIssueExactSet(
ISetToken _setToken,
uint256 _setAmount,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
returns (uint256)
{
morphoLeverageModule.sync(_setToken);
LeveragedTokenData memory issueInfo = _getLeveragedTokenData(_setToken, _setAmount, true);
uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
uint256 borrowSaleProceeds = DEXAdapterV5.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
collateralOwed = collateralOwed.sub(borrowSaleProceeds);
return DEXAdapterV5.getAmountIn(addresses, _swapDataInputToken, collateralOwed, _maxAmountInputToken);
}
/**
* Gets the proceeds of a redemption of a given amount of a set token. This
* function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter
* contract and call sync on MorphoLeverageModule. Note: If the two SwapData
* paths contain the same tokens, there will be a slight error introduced
* in the result.
*
* @param _setToken the set token to issue
* @param _setAmount amount of set tokens
* @param _swapDataCollateralForDebt swap data for the collateral to debt swap
* @param _swapDataOutputToken swap data for the collateral token to the output token
*
* @return amount of _outputToken that would be obtained from the redemption
*/
function getRedeemExactSet(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
returns (uint256)
{
morphoLeverageModule.sync(_setToken);
LeveragedTokenData memory redeemInfo = _getLeveragedTokenData(_setToken, _setAmount, false);
uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
uint256 debtPurchaseCost = DEXAdapterV5.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed, redeemInfo.collateralAmount);
uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
return DEXAdapterV5.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
DEXAdapterV5.ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
external
virtual
nonReentrant
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
virtual
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
external
virtual
payable
nonReentrant
{
_initiateIssuance(
_setToken,
_setAmount,
DEXAdapterV5.ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken
);
}
/**
* Callback function called by Morpho after flashloan has been requested
*
* @param assets Amount of tokens loaned / to be repayed
* @param data Encoded data containing the original sender and leveraged token data
*/
function onMorphoFlashLoan(uint256 assets, bytes calldata data) external {
require(msg.sender == address(morpho));
DecodedParams memory decodedParams = abi.decode(data, (DecodedParams));
require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
if(decodedParams.isIssuance){
_performIssuance(decodedParams.leveragedTokenData.collateralToken, assets, decodedParams);
IERC20(decodedParams.leveragedTokenData.collateralToken).approve(address(morpho), assets);
} else {
_performRedemption(decodedParams.leveragedTokenData.debtToken, assets, decodedParams);
IERC20(decodedParams.leveragedTokenData.debtToken).approve(address(morpho), assets);
}
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true);
_approveToken(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
}
/* ============ Internal Functions ============ */
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _collateralToken Address of the underlying collateral token that was loaned
* @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
address _collateralToken,
uint256 _collateralTokenAmountNet,
DecodedParams memory _decodedParams
)
internal
{
_issueSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
uint amountInputTokenSpent = _obtainCollateralTokens(
_collateralToken,
_collateralTokenAmountNet,
_decodedParams
);
require(amountInputTokenSpent <= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT INPUT AMOUNT");
emit FlashMint(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountInputTokenSpent,
_decodedParams.setAmount
);
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _debtToken Address of the debt token that was loaned
* @param _debtTokenAmountNet Amount of debt token that was received as flashloan
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
address _debtToken,
uint256 _debtTokenAmountNet,
DecodedParams memory _decodedParams
)
internal
{
// Redeem set using debt tokens obtained from flashloan
_redeemSet(
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender
);
// Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
uint256 collateralTokenSpent = _swapCollateralForDebtToken(
_debtTokenAmountNet,
_debtToken,
_decodedParams.leveragedTokenData.collateralAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Liquidate remaining collateral tokens for the payment token specified by user
uint256 amountOutputToken = _liquidateCollateralTokens(
collateralTokenSpent,
_decodedParams.setToken,
_decodedParams.setAmount,
_decodedParams.originalSender,
_decodedParams.paymentToken,
_decodedParams.limitAmount,
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
_decodedParams.paymentTokenSwapData
);
require(amountOutputToken >= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
emit FlashRedeem(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
amountOutputToken,
_decodedParams.setAmount
);
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(debtPositions[1] > 0){
return LeveragedTokenData(
components[0],
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
components[1],
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapDataDebtForCollateral,
DEXAdapterV5.SwapData memory _swapDataInputToken
)
internal
{
morphoLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(leveragedTokenData.collateralToken, leveragedTokenData.collateralAmount, params);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapDataCollateralForDebt,
DEXAdapterV5.SwapData memory _swapDataOutputToken
)
internal
{
morphoLeverageModule.sync(_setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(leveragedTokenData.debtToken, leveragedTokenData.debtAmount, params);
}
/**
* Gets rid of the obtained collateral tokens from redemption by either sending them to the user
* directly or converting them to the payment token and sending those out.
*
* @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Address of the user who initiated the redemption
* @param _outputToken Address of token to return to the user
* @param _collateralToken Address of the collateral token to sell
* @param _collateralAmount Amount of collateral token to sell
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapterV5.SwapData memory _swapData
)
internal
returns (uint256)
{
require(_collateralAmount >= _collateralTokenSpent, "ExchangeIssuance: OVERSPENT COLLATERAL TOKEN");
uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
uint256 outputAmount;
if(_outputToken == DEXAdapterV5.ETH_ADDRESS){
outputAmount = _liquidateCollateralTokensForETH(
_collateralToken,
amountToReturn,
_originalSender,
_minAmountOutputToken,
_swapData
);
} else {
outputAmount = _liquidateCollateralTokensForERC20(
_collateralToken,
amountToReturn,
_originalSender,
IERC20(_outputToken),
_minAmountOutputToken,
_swapData
);
}
return outputAmount;
}
/**
* Returns the collateralToken directly to the user
*
* @param _collateralToken Address of the the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
*/
function _returnCollateralTokensToSender(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender
)
internal
{
IERC20(_collateralToken).transfer(_originalSender, _collateralRemaining);
}
/**
* Sells the collateral tokens for the selected output ERC20 and returns that to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to Output token
*
* @return Amount of output token returned to the user
*/
function _liquidateCollateralTokensForERC20(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
IERC20 _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_outputToken) == _collateralToken){
_returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
return _collateralRemaining;
}
uint256 outputTokenAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
address(_outputToken),
_minAmountOutputToken,
_swapData
);
_outputToken.transfer(_originalSender, outputTokenAmount);
return outputTokenAmount;
}
/**
* Sells the remaining collateral tokens for weth, withdraws that and returns native eth to the user
*
* @param _collateralToken Address of the collateral token
* @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
* @param _originalSender Address of the original sender to return the eth to
* @param _minAmountOutputToken Minimum amount of output token to return to user
* @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to eth
*
* @return Amount of eth returned to the user
*/
function _liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
isValidPath(_swapData.path, _collateralToken, addresses.weth)
returns(uint256)
{
uint256 ethAmount = _swapCollateralForOutputToken(
_collateralToken,
_collateralRemaining,
addresses.weth,
_minAmountOutputToken,
_swapData
);
if (ethAmount > 0) {
IWETH(addresses.weth).withdraw(ethAmount);
(payable(_originalSender)).sendValue(ethAmount);
}
return ethAmount;
}
/**
* Obtains the tokens necessary to return the flashloan by swapping the debt tokens obtained
* from issuance and making up the shortfall using the users funds.
*
* @param _collateralToken collateral token to obtain
* @param _amountRequired Amount of collateralToken required to repay the flashloan
* @param _decodedParams Struct containing decoded data from original call passed through via flashloan
*
* @return Amount of input token spent
*/
function _obtainCollateralTokens(
address _collateralToken,
uint256 _amountRequired,
DecodedParams memory _decodedParams
)
internal
returns (uint256)
{
uint collateralTokenObtained = _swapDebtForCollateralToken(
_collateralToken,
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.leveragedTokenData.debtAmount,
_decodedParams.collateralAndDebtSwapData
);
uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
uint amountInputToken;
if(_decodedParams.paymentToken == DEXAdapterV5.ETH_ADDRESS){
amountInputToken = _makeUpShortfallWithETH(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
} else {
amountInputToken = _makeUpShortfallWithERC20(
_collateralToken,
collateralTokenShortfall,
_decodedParams.originalSender,
IERC20(_decodedParams.paymentToken),
_decodedParams.limitAmount,
_decodedParams.paymentTokenSwapData
);
}
return amountInputToken;
}
/**
* Issues set token using the previously obtained collateral token
* Results in debt token being returned to the contract
*
* @param _setToken Address of the SetToken to be issued
* @param _setAmount Amount of SetTokens to issue
* @param _originalSender Adress that initiated the token issuance, which will receive the set tokens
*/
function _issueSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
}
/**
* Redeems set token using the previously obtained debt token
* Results in collateral token being returned to the contract
*
* @param _setToken Address of the SetToken to be redeemed
* @param _setAmount Amount of SetTokens to redeem
* @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
*/
function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
_setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
}
/**
* Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
* from swapping the debt tokens from the users address
*
* @param _token Address of the token to transfer from user
* @param _shortfall Collateral token shortfall required to return the flashloan
* @param _originalSender Adress that initiated the token issuance, which is the adresss form which to transfer the tokens
*/
function _transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
internal
{
if(_shortfall>0){
IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
}
}
/**
* Makes up the collateral token shortfall with user specified ERC20 token
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
*
* @return Amount of input token spent
*/
function _makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns (uint256)
{
if(address(_inputToken) == _collateralToken){
_transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
return _collateralTokenShortfall;
} else {
_inputToken.transferFrom(_originalSender, address(this), _maxAmountInputToken);
uint256 amountInputToken = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
address(_inputToken),
_maxAmountInputToken,
_swapData
);
if(amountInputToken < _maxAmountInputToken){
_inputToken.transfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
}
return amountInputToken;
}
}
/**
* Makes up the collateral token shortfall with native eth
*
* @param _collateralToken Address of the collateral token
* @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
* @param _originalSender Address of the original sender to return the tokens to
* @param _maxAmountEth Maximum amount of eth to pay
*
* @return Amount of eth spent
*/
function _makeUpShortfallWithETH(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
uint256 _maxAmountEth,
DEXAdapterV5.SwapData memory _swapData
)
internal
virtual
returns(uint256)
{
IWETH(addresses.weth).deposit{value: _maxAmountEth}();
uint256 amountEth = _swapInputForCollateralToken(
_collateralToken,
_collateralTokenShortfall,
addresses.weth,
_maxAmountEth,
_swapData
);
if(_maxAmountEth > amountEth){
uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
IWETH(addresses.weth).withdraw(amountEthReturn);
(payable(_originalSender)).sendValue(amountEthReturn);
}
return amountEth;
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _collateralToken Address of the collateral token buy
* @param _debtToken Address of the debt token to sell
* @param _debtAmount Amount of debt token to sell
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token obtained
*/
function _swapDebtForCollateralToken(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _debtToken, _collateralToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_debtAmount,
// minAmountOut is 0 here since we are going to make up the shortfall with the input token.
// Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
0,
_swapData
);
}
/**
* Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption
*
* @param _debtAmount Amount of debt token to buy
* @param _debtToken Address of debt token
* @param _collateralAmount Amount of collateral token available to spend / used as maxAmountIn parameter
* @param _collateralToken Address of collateral token
* @param _swapData Struct containing path and fee data for swap
*
* @return Amount of collateral token spent
*/
function _swapCollateralForDebtToken(
uint256 _debtAmount,
address _debtToken,
uint256 _collateralAmount,
address _collateralToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _debtToken)
returns (uint256)
{
return addresses.swapTokensForExactTokens(
_debtAmount,
_collateralAmount,
_swapData
);
}
/**
* Acquires the required amount of collateral tokens by swapping the input tokens
* Does nothing if collateral and input token are indentical
*
* @param _collateralToken Address of collateral token
* @param _amountRequired Remaining amount of collateral token required to repay flashloan, after having swapped debt tokens for collateral
* @param _inputToken Address of input token to swap
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of input token spent
*/
function _swapInputForCollateralToken(
address _collateralToken,
uint256 _amountRequired,
address _inputToken,
uint256 _maxAmountInputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(
_swapData.path,
_inputToken,
_collateralToken
)
returns (uint256)
{
if(_collateralToken == _inputToken) return _amountRequired;
return addresses.swapTokensForExactTokens(
_amountRequired,
_maxAmountInputToken,
_swapData
);
}
/**
* Swaps the collateral tokens obtained from redemption for the selected output token
* If both tokens are the same, does nothing
*
* @param _collateralToken Address of collateral token
* @param _collateralTokenAmount Amount of colalteral token to swap
* @param _outputToken Address of the ERC20 token to swap into
* @param _minAmountOutputToken Minimum amount of output token to return to the user
* @param _swapData Data (token addresses and fee levels) describing the swap path
*
* @return Amount of output token obtained
*/
function _swapCollateralForOutputToken(
address _collateralToken,
uint256 _collateralTokenAmount,
address _outputToken,
uint256 _minAmountOutputToken,
DEXAdapterV5.SwapData memory _swapData
)
internal
isValidPath(_swapData.path, _collateralToken, _outputToken)
returns (uint256)
{
return addresses.swapExactTokensForTokens(
_collateralTokenAmount,
_minAmountOutputToken,
_swapData
);
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Triggers the flashloan from the Morpho Vault
*
* @param token Address of the token to loan
* @param amount Amount to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address token,
uint256 amount,
bytes memory params
)
internal
{
require(flashLoanBenefactor == address(0), "Flashloan already taken");
flashLoanBenefactor = msg.sender;
morpho.flashLoan(token, amount, params);
flashLoanBenefactor = address(0);
}
/**
* Redeems a given amount of SetToken.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
debtIssuanceModule.redeem(_setToken, _amount, address(this));
}
receive() external payable {}
}
FlashmintLeveragedZeroEx.sol 882 lines
/*
Copyright 2025 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IMorphoLeverageModule } from "../interfaces/IMorphoLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { IMorpho } from "../interfaces/IMorpho.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import {IPool} from "../interfaces/IPool.sol";
/**
* @title FlashMintLeveragedZeroEx
* @author Index Coop
*
* Contract for issuing and redeeming a leveraged Set Token
* Supports all standard (1 collateral / 1 debt token) leveraged tokens on either morpho or aave leveragemodule
* Both the collateral as well as the debt token have to be available for flashloan from morpho and be
* tradeable against each other via one of the whitelisted swap target contracts
*/
contract FlashMintLeveragedZeroEx is ReentrancyGuard, Ownable {
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralToken;
address collateralAToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct SwapData {
address swapTarget;
bytes callData;
}
struct DecodedParams {
ISetToken setToken;
bool isAave;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
SwapData collateralAndDebtSwapData;
SwapData paymentTokenSwapData;
}
struct TokenBalance {
IERC20 token;
uint256 balance;
}
/* ============ Constants ============= */
uint256 private constant MAX_UINT256 = type(uint256).max;
address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ State Variables ============ */
uint256 public immutable ROUNDING_ERROR_MARGIN;
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IMorphoLeverageModule public immutable morphoLeverageModule;
IMorphoLeverageModule public immutable aaveLeverageModule;
IMorpho public immutable morpho;
IWETH public immutable weth;
IPool public immutable aavePool;
address private flashLoanBenefactor;
// TODO: Add support for multiple routers supplied by the user
mapping(address => bool) public swapTargetWhitelist;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed // The amount of SetTokens redeemed for output tokens
);
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _morphoLeverageModule MorphoLeverageModule to sync before every issuance / redemption
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _morpho Morpho contract to call for flashloan
* @param _weth WETH contract to deposit and withdraw eth
* @param _swapTarget Address of the 0x router to use for swaps
*/
constructor(
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IMorphoLeverageModule _morphoLeverageModule,
IMorphoLeverageModule _aaveLeverageModule,
IMorpho _morpho,
IPool _aavePool,
IWETH _weth,
address _swapTarget,
uint256 _roundingErrorMargin
)
public
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
morphoLeverageModule = _morphoLeverageModule;
aaveLeverageModule = _aaveLeverageModule;
morpho = _morpho;
aavePool = _aavePool;
weth = _weth;
swapTargetWhitelist[_swapTarget] = true;
ROUNDING_ERROR_MARGIN = _roundingErrorMargin;
}
/* ============ External Functions ============ */
/**
* Adds or removes a given swapTarget from the whitelist
* OWNER ONLY
*
* @param _swapTarget Settlement contract to add/remove from whitelist
* @param _isAllowed Boolean indicating wether given contract should be included in the whitelist
*
*/
function setSwapTargetWhitelist(address _swapTarget, bool _isAllowed) external onlyOwner {
swapTargetWhitelist[_swapTarget] = _isAllowed;
}
/**
* Withdraws stranded tokens from the contracts balance
* OWNER ONLY
*
* @param _token Token to be withdrawn from the contract balance
*
*/
function withdrawToken(IERC20 _token) external onlyOwner {
if (address(_token) == address(0)) {
msg.sender.sendValue(address(this).balance);
} else {
_token.safeTransfer(msg.sender, _token.balanceOf(address(this)));
}
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
* @param _isAave Boolean indicating wether given leveraged token is based on aave leverage module (or morpho)
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance,
bool _isAave
)
external
returns (LeveragedTokenData memory)
{
_syncLeverageModule(_isAave, _setToken);
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance, _isAave);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
SwapData memory _swapDataCollateralForDebt,
SwapData memory _swapDataOutputToken,
bool _isAave
)
external
virtual
nonReentrant
returns(uint256[] memory)
{
_initiateRedemption(
_setToken,
_setAmount,
ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken,
_isAave
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
SwapData memory _swapDataCollateralForDebt,
SwapData memory _swapDataOutputToken,
bool _isAave
)
external
virtual
nonReentrant
returns(uint256[] memory)
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken,
_isAave
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
SwapData memory _swapDataDebtForCollateral,
SwapData memory _swapDataInputToken,
bool _isAave
)
external
virtual
nonReentrant
returns(uint256[] memory)
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken,
_isAave
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
SwapData memory _swapDataDebtForCollateral,
SwapData memory _swapDataInputToken,
bool _isAave
)
external
virtual
payable
nonReentrant
returns(uint256[] memory)
{
_initiateIssuance(
_setToken,
_setAmount,
ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken,
_isAave
);
}
/**
* Callback function called by Morpho after flashloan has been requested
*
* @param assets Amount of tokens loaned / to be repayed
* @param data Encoded data containing the original sender and leveraged token data
*/
function onMorphoFlashLoan(uint256 assets, bytes calldata data) external {
require(msg.sender == address(morpho));
DecodedParams memory decodedParams = abi.decode(data, (DecodedParams));
require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
if(decodedParams.isIssuance){
_performIssuance(decodedParams);
IERC20(decodedParams.leveragedTokenData.collateralToken).approve(address(morpho), assets);
} else {
_performRedemption(decodedParams);
IERC20(decodedParams.leveragedTokenData.debtToken).approve(address(morpho), assets);
}
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
* @param _isAave Boolean indicating wether leverage token is based on aave leverage module
*/
function approveSetToken(ISetToken _setToken, bool _isAave) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true, _isAave);
if(_isAave){
_approveToken(IERC20(leveragedTokenData.collateralAToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
} else {
_approveToken(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
}
}
/* ============ Internal Functions ============ */
function _getTokenBalances(address _paymentToken, LeveragedTokenData memory leveragedTokenData)
internal
returns (uint256[] memory tokenBalances)
{
bool isPaymentTokenDistinct = _paymentToken != leveragedTokenData.collateralToken && _paymentToken != leveragedTokenData.debtToken;
tokenBalances = isPaymentTokenDistinct ? new uint256[](3) : new uint256[](2);
tokenBalances[0] = IERC20(leveragedTokenData.collateralToken).balanceOf(address(this));
tokenBalances[1] = IERC20(leveragedTokenData.debtToken).balanceOf(address(this));
if (isPaymentTokenDistinct) {
if(_paymentToken == ETH_ADDRESS) {
tokenBalances[2] = address(this).balance;
} else {
tokenBalances[2] = IERC20(_paymentToken).balanceOf(address(this));
}
}
}
function _returnExcessTokenBalances(uint256[] memory balancesBefore, address _paymentToken, LeveragedTokenData memory leveragedTokenData)
internal
returns(uint256[] memory amountsReturned)
{
amountsReturned = new uint256[](balancesBefore.length);
if(balancesBefore.length > 2) {
if(_paymentToken == ETH_ADDRESS) {
uint256 paymentTokenBalance = weth.balanceOf(address(this));
if(paymentTokenBalance > balancesBefore[2]) {
amountsReturned[2] = paymentTokenBalance - balancesBefore[2];
weth.withdraw(amountsReturned[2]);
msg.sender.sendValue(amountsReturned[2]);
}
} else {
uint256 paymentTokenBalance = IERC20(_paymentToken).balanceOf(address(this));
if(paymentTokenBalance > balancesBefore[2]) {
amountsReturned[2] = paymentTokenBalance - balancesBefore[2];
IERC20(_paymentToken).safeTransfer(msg.sender, amountsReturned[2]);
}
}
}
uint256 collateralTokenBalance = IERC20(leveragedTokenData.collateralToken).balanceOf(address(this));
if(collateralTokenBalance > balancesBefore[0]) {
amountsReturned[0] = collateralTokenBalance - balancesBefore[0];
if(leveragedTokenData.collateralToken == address(weth) && _paymentToken == ETH_ADDRESS) {
weth.withdraw(amountsReturned[0]);
msg.sender.sendValue(amountsReturned[0]);
} else {
IERC20(leveragedTokenData.collateralToken).safeTransfer(msg.sender, amountsReturned[0]);
}
}
uint256 debtTokenBalance = IERC20(leveragedTokenData.debtToken).balanceOf(address(this));
if(debtTokenBalance > balancesBefore[1]) {
amountsReturned[1] = debtTokenBalance - balancesBefore[1];
if(leveragedTokenData.debtToken == address(weth) && _paymentToken == ETH_ADDRESS) {
weth.withdraw(amountsReturned[1]);
msg.sender.sendValue(amountsReturned[1]);
} else {
IERC20(leveragedTokenData.debtToken).safeTransfer(msg.sender, amountsReturned[1]);
}
}
}
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
DecodedParams memory _decodedParams
)
internal
{
if(_decodedParams.isAave) {
// Deposit collateral token obtained from flashloan to get the respective aToken position required for issuance
_depositCollateralToken(_decodedParams.leveragedTokenData.collateralToken, _decodedParams.leveragedTokenData.collateralAmount);
}
debtIssuanceModule.issue(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
_executeSwapData(
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.collateralAndDebtSwapData
);
address inputToken;
if(_decodedParams.paymentToken == ETH_ADDRESS) {
weth.deposit{value: _decodedParams.limitAmount}();
inputToken = address(weth);
} else {
inputToken = _decodedParams.paymentToken;
// Security Assumption: No one can manipulate Morpho such that this original sender is not the original sender of the transaction
// Alternatively:
IERC20(inputToken).safeTransferFrom(_decodedParams.originalSender, address(this), _decodedParams.limitAmount);
}
_executeSwapData(
inputToken,
_decodedParams.paymentTokenSwapData
);
emit FlashMint(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
_decodedParams.setAmount
);
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
DecodedParams memory _decodedParams
)
internal
{
debtIssuanceModule.redeem(_decodedParams.setToken, _decodedParams.setAmount, address(this));
if(_decodedParams.isAave) {
// Withdraw underlying collateral token from the aToken position returned by redeem step
_withdrawCollateralToken(
_decodedParams.leveragedTokenData.collateralToken
);
}
// Swap Collateral for Debt Tokens
_executeSwapData(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Swap Collateral tokens for Payment token
_executeSwapData(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.paymentTokenSwapData
);
emit FlashRedeem(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
_decodedParams.setAmount
);
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
* @param _isAave Boolean indicating wether given leveraged token is based on aave leverage module (or morpho)
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance,
bool _isAave
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
return LeveragedTokenData(
_isAave ? IAToken(components[0]).UNDERLYING_ASSET_ADDRESS() : components[0],
_isAave ? components[0] : address(0),
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
_isAave ? IAToken(components[1]).UNDERLYING_ASSET_ADDRESS() : components[1],
_isAave ? components[1] : address(0),
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Approves max amount of token to lending pool
*
* @param _token Address of the token to approve
*/
function _approveTokenToLendingPool(
IERC20 _token
)
internal
{
uint256 allowance = _token.allowance(address(this), address(aavePool));
if (allowance > 0) {
_token.approve(address(aavePool), 0);
}
_token.approve(address(aavePool), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
SwapData memory _swapDataDebtForCollateral,
SwapData memory _swapDataInputToken,
bool _isAave
)
internal
returns(uint256[] memory)
{
_syncLeverageModule(_isAave, _setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true, _isAave);
uint256[] memory tokenBalances = _getTokenBalances(_inputToken, leveragedTokenData);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_isAave,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(leveragedTokenData.collateralToken, leveragedTokenData.collateralAmount, params);
return _returnExcessTokenBalances(tokenBalances, _inputToken, leveragedTokenData);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
* @param _isAave Boolean indicating wether given leverage token is based on aave leverage module
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
SwapData memory _swapDataCollateralForDebt,
SwapData memory _swapDataOutputToken,
bool _isAave
)
internal
returns(uint256[] memory returnedAmounts)
{
_syncLeverageModule(_isAave, _setToken);
_setToken.safeTransferFrom(msg.sender, address(this), _setAmount);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false, _isAave);
uint256[] memory tokenBalances = _getTokenBalances(_outputToken, leveragedTokenData);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_isAave,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(leveragedTokenData.debtToken, leveragedTokenData.debtAmount, params);
returnedAmounts = _returnExcessTokenBalances(tokenBalances, _outputToken, leveragedTokenData);
require(
(_outputToken == leveragedTokenData.collateralToken && returnedAmounts[0] >= _minAmountOutputToken)
||
(_outputToken == leveragedTokenData.debtToken && returnedAmounts[1] >= _minAmountOutputToken)
||
(returnedAmounts[2] >= _minAmountOutputToken),
"INSUFFICIENT OUTPUT AMOUNT"
);
}
function _syncLeverageModule(
bool _isAave,
ISetToken _setToken
)
internal {
if(_isAave) {
aaveLeverageModule.sync(_setToken);
} else {
morphoLeverageModule.sync(_setToken);
}
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _inputToken Input token to be approved for this swap
* @param _swapData Struct containing path and fee data for swap
*
*/
function _executeSwapData(
address _inputToken,
SwapData memory _swapData
)
internal
{
if(_swapData.swapTarget != address(0)){
IERC20(_inputToken).approve(_swapData.swapTarget, IERC20(_inputToken).balanceOf(address(this)));
_fillQuote(_swapData);
}
}
/**
* Execute a 0x Swap quote
*
* @param _quote Swap quote as returned by 0x API
*
*/
function _fillQuote(
SwapData memory _quote
)
internal
{
require(swapTargetWhitelist[_quote.swapTarget], "swapTarget not whitelisted");
(bool success, bytes memory returndata) = _quote.swapTarget.call(_quote.callData);
// Forwarding errors including new custom errors
// Taken from: https://ethereum.stackexchange.com/a/111187/73805
if (!success) {
if (returndata.length == 0) revert();
assembly {
revert(add(32, returndata), mload(returndata))
}
}
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Triggers the flashloan from the BalancerV2 Vault
*
* @param token Address of the token to loan
* @param amount Amount to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address token,
uint256 amount,
bytes memory params
)
internal
{
require(flashLoanBenefactor == address(0), "Flashloan already taken");
flashLoanBenefactor = msg.sender;
morpho.flashLoan(token, amount, params);
flashLoanBenefactor = address(0);
}
/**
* Deposit collateral to aave to obtain collateralAToken for issuance
*
* @param _collateralToken Address of collateral token
* @param _depositAmount Amount to deposit
*/
function _depositCollateralToken(
address _collateralToken,
uint256 _depositAmount
) internal {
aavePool.deposit(_collateralToken, _depositAmount, address(this), 0);
}
/**
* Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
*
* @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
*/
function _withdrawCollateralToken(
address _collateralToken
) internal {
// Withdraw full aToken balance
aavePool.withdraw(_collateralToken, type(uint256).max, address(this));
}
receive() external payable {}
}
FlashMintLeveragedZeroExBalancerFL.sol 906 lines
/*
Copyright 2025 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IMorphoLeverageModule } from "../interfaces/IMorphoLeverageModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import {IVault, IFlashLoanRecipient} from "../interfaces/external/balancer-v2/IVault.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IAToken } from "../interfaces/IAToken.sol";
import {IPool} from "../interfaces/IPool.sol";
/**
* @title FlashMintLeveragedZeroEx
* @author Index Coop
*
* Contract for issuing and redeeming a leveraged Set Token using externally encoded calldata
* Saame as FlashMintLeveragedZeroEx but with balancer flashloan
*/
contract FlashMintLeveragedZeroExBalancerFL is ReentrancyGuard, Ownable, IFlashLoanRecipient {
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct LeveragedTokenData {
address collateralToken;
address collateralAToken;
uint256 collateralAmount;
address debtToken;
uint256 debtAmount;
}
struct SwapData {
address swapTarget;
bytes callData;
}
struct DecodedParams {
ISetToken setToken;
bool isAave;
uint256 setAmount;
address originalSender;
bool isIssuance;
address paymentToken;
uint256 limitAmount;
LeveragedTokenData leveragedTokenData;
SwapData collateralAndDebtSwapData;
SwapData paymentTokenSwapData;
}
struct TokenBalance {
IERC20 token;
uint256 balance;
}
/* ============ Constants ============= */
uint256 private constant MAX_UINT256 = type(uint256).max;
uint256 public constant ROUNDING_ERROR_MARGIN = 2;
address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ State Variables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable debtIssuanceModule;
IMorphoLeverageModule public immutable morphoLeverageModule;
IMorphoLeverageModule public immutable aaveLeverageModule;
IVault public immutable balancerV2Vault;
IWETH public immutable weth;
IPool public immutable aavePool;
address private flashLoanBenefactor;
// TODO: Add support for multiple routers supplied by the user
mapping(address => bool) public swapTargetWhitelist;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the SetTokens
ISetToken indexed _setToken, // The redeemed SetToken
address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed // The amount of SetTokens redeemed for output tokens
);
modifier onlyBalancerV2Vault() {
require(msg.sender == address(balancerV2Vault), "ExchangeIssuance: BalancerV2 Vault ONLY");
_;
}
/* ============ Constructor ============ */
/**
* Sets various contract addresses
*
* @param _setController SetToken controller used to verify a given token is a set
* @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
* @param _morphoLeverageModule MorphoLeverageModule to sync before every issuance / redemption
* @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
* @param _balancerV2Vault Balancer vault to get flashloans from
* @param _weth WETH contract to deposit and withdraw eth
* @param _swapTarget Address of the 0x router to use for swaps
*/
constructor(
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
IMorphoLeverageModule _morphoLeverageModule,
IMorphoLeverageModule _aaveLeverageModule,
IVault _balancerV2Vault,
IPool _aavePool,
IWETH _weth,
address _swapTarget
)
public
{
setController = _setController;
debtIssuanceModule = _debtIssuanceModule;
morphoLeverageModule = _morphoLeverageModule;
aaveLeverageModule = _aaveLeverageModule;
balancerV2Vault = _balancerV2Vault;
aavePool = _aavePool;
weth = _weth;
swapTargetWhitelist[_swapTarget] = true;
}
/* ============ External Functions ============ */
/**
* Adds or removes a given swapTarget from the whitelist
* OWNER ONLY
*
* @param _swapTarget Settlement contract to add/remove from whitelist
* @param _isAllowed Boolean indicating wether given contract should be included in the whitelist
*
*/
function setSwapTargetWhitelist(address _swapTarget, bool _isAllowed) external onlyOwner {
swapTargetWhitelist[_swapTarget] = _isAllowed;
}
/**
* Withdraws stranded tokens from the contracts balance
* OWNER ONLY
*
* @param _token Token to be withdrawn from the contract balance
*
*/
function withdrawToken(IERC20 _token) external onlyOwner {
if (address(_token) == address(0)) {
msg.sender.sendValue(address(this).balance);
} else {
_token.safeTransfer(msg.sender, _token.balanceOf(address(this)));
}
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
* @param _isAave Boolean indicating wether given leveraged token is based on aave leverage module (or morpho)
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance,
bool _isAave
)
external
returns (LeveragedTokenData memory)
{
_syncLeverageModule(_isAave, _setToken);
return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance, _isAave);
}
/**
* Runs all the necessary approval functions required for a given ERC20 token.
* This function can be called when a new token is added to a SetToken during a
* rebalance.
*
* @param _token Address of the token which needs approval
*/
function approveToken(IERC20 _token) external {
_approveToken(_token);
}
/**
* Trigger redemption of set token to pay the user with Eth
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _minAmountOutputToken Minimum amount of ETH to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setAmount,
uint256 _minAmountOutputToken,
SwapData memory _swapDataCollateralForDebt,
SwapData memory _swapDataOutputToken,
bool _isAave
)
external
virtual
nonReentrant
returns(uint256[] memory)
{
_initiateRedemption(
_setToken,
_setAmount,
ETH_ADDRESS,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken,
_isAave
);
}
/**
* Trigger redemption of set token to pay the user with an arbitrary ERC20
*
* @param _setToken Set token to redeem
* @param _setAmount Amount to redeem
* @param _outputToken Address of the ERC20 token to send to the user
* @param _minAmountOutputToken Minimum amount of output token to send to the user
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
SwapData memory _swapDataCollateralForDebt,
SwapData memory _swapDataOutputToken,
bool _isAave
)
external
virtual
nonReentrant
returns(uint256[] memory)
{
_initiateRedemption(
_setToken,
_setAmount,
_outputToken,
_minAmountOutputToken,
_swapDataCollateralForDebt,
_swapDataOutputToken,
_isAave
);
}
/**
* Trigger issuance of set token paying with any arbitrary ERC20 token
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _inputToken Input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to spend
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function issueExactSetFromERC20(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
SwapData memory _swapDataDebtForCollateral,
SwapData memory _swapDataInputToken,
bool _isAave
)
external
virtual
nonReentrant
returns(uint256[] memory)
{
_initiateIssuance(
_setToken,
_setAmount,
_inputToken,
_maxAmountInputToken,
_swapDataDebtForCollateral,
_swapDataInputToken,
_isAave
);
}
/**
* Trigger issuance of set token paying with Eth
*
* @param _setToken Set token to issue
* @param _setAmount Amount to issue
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _setAmount,
SwapData memory _swapDataDebtForCollateral,
SwapData memory _swapDataInputToken,
bool _isAave
)
external
virtual
payable
nonReentrant
returns(uint256[] memory)
{
_initiateIssuance(
_setToken,
_setAmount,
ETH_ADDRESS,
msg.value,
_swapDataDebtForCollateral,
_swapDataInputToken,
_isAave
);
}
/**
* This is the callback function that will be called by the Balancerv2 Pool after flashloaned tokens have been sent
* to this contract.
* After exiting this function the Vault enforces that we transfer back the loaned tokens + interest. If that check fails
* the whole transaction gets reverted
*
* @param tokens Addresses of all assets that were borrowed
* @param amounts Amounts that were borrowed
* @param feeAmounts Interest to be paid on top of borrowed amount
* @param userData Encoded bytestring of other parameters from the original contract call to be used downstream
*
*/
function receiveFlashLoan(
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
)
external
override
onlyBalancerV2Vault
{
DecodedParams memory decodedParams = abi.decode(userData, (DecodedParams));
require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
if(decodedParams.isIssuance){
_performIssuance(decodedParams);
} else {
_performRedemption(decodedParams);
}
for(uint256 i = 0; i < tokens.length; i++) {
tokens[i].safeTransfer(address(balancerV2Vault), amounts[i]+ feeAmounts[i]);
}
}
/**
* Runs all the necessary approval functions required for a list of ERC20 tokens.
*
* @param _tokens Addresses of the tokens which need approval
*/
function approveTokens(IERC20[] memory _tokens) external {
for (uint256 i = 0; i < _tokens.length; i++) {
_approveToken(_tokens[i]);
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
* @param _isAave Boolean indicating wether leverage token is based on aave leverage module
*/
function approveSetToken(ISetToken _setToken, bool _isAave) external {
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true, _isAave);
if(_isAave){
_approveToken(IERC20(leveragedTokenData.collateralAToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
_approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
} else {
_approveToken(IERC20(leveragedTokenData.collateralToken));
_approveToken(IERC20(leveragedTokenData.debtToken));
}
}
/* ============ Internal Functions ============ */
function _getTokenBalances(address _paymentToken, LeveragedTokenData memory leveragedTokenData)
internal
returns (uint256[] memory tokenBalances)
{
bool isPaymentTokenDistinct = _paymentToken != leveragedTokenData.collateralToken && _paymentToken != leveragedTokenData.debtToken;
tokenBalances = isPaymentTokenDistinct ? new uint256[](3) : new uint256[](2);
tokenBalances[0] = IERC20(leveragedTokenData.collateralToken).balanceOf(address(this));
tokenBalances[1] = IERC20(leveragedTokenData.debtToken).balanceOf(address(this));
if (isPaymentTokenDistinct) {
if(_paymentToken == ETH_ADDRESS) {
tokenBalances[2] = address(this).balance;
} else {
tokenBalances[2] = IERC20(_paymentToken).balanceOf(address(this));
}
}
}
function _returnExcessTokenBalances(uint256[] memory balancesBefore, address _paymentToken, LeveragedTokenData memory leveragedTokenData)
internal
returns(uint256[] memory amountsReturned)
{
amountsReturned = new uint256[](balancesBefore.length);
if(balancesBefore.length > 2) {
if(_paymentToken == ETH_ADDRESS) {
uint256 paymentTokenBalance = weth.balanceOf(address(this));
if(paymentTokenBalance > balancesBefore[2]) {
amountsReturned[2] = paymentTokenBalance - balancesBefore[2];
weth.withdraw(amountsReturned[2]);
msg.sender.sendValue(amountsReturned[2]);
}
} else {
uint256 paymentTokenBalance = IERC20(_paymentToken).balanceOf(address(this));
if(paymentTokenBalance > balancesBefore[2]) {
amountsReturned[2] = paymentTokenBalance - balancesBefore[2];
IERC20(_paymentToken).safeTransfer(msg.sender, amountsReturned[2]);
}
}
}
uint256 collateralTokenBalance = IERC20(leveragedTokenData.collateralToken).balanceOf(address(this));
if(collateralTokenBalance > balancesBefore[0]) {
amountsReturned[0] = collateralTokenBalance - balancesBefore[0];
if(leveragedTokenData.collateralToken == address(weth) && _paymentToken == ETH_ADDRESS) {
weth.withdraw(amountsReturned[0]);
msg.sender.sendValue(amountsReturned[0]);
} else {
IERC20(leveragedTokenData.collateralToken).safeTransfer(msg.sender, amountsReturned[0]);
}
}
uint256 debtTokenBalance = IERC20(leveragedTokenData.debtToken).balanceOf(address(this));
if(debtTokenBalance > balancesBefore[1]) {
amountsReturned[1] = debtTokenBalance - balancesBefore[1];
if(leveragedTokenData.debtToken == address(weth) && _paymentToken == ETH_ADDRESS) {
weth.withdraw(amountsReturned[1]);
msg.sender.sendValue(amountsReturned[1]);
} else {
IERC20(leveragedTokenData.debtToken).safeTransfer(msg.sender, amountsReturned[1]);
}
}
}
/**
* Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
*
* @param _decodedParams Struct containing token addresses / amounts to perform issuance
*/
function _performIssuance(
DecodedParams memory _decodedParams
)
internal
{
if(_decodedParams.isAave) {
// Deposit collateral token obtained from flashloan to get the respective aToken position required for issuance
_depositCollateralToken(_decodedParams.leveragedTokenData.collateralToken, _decodedParams.leveragedTokenData.collateralAmount);
}
debtIssuanceModule.issue(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
// Obtain necessary collateral tokens to repay flashloan
_executeSwapData(
_decodedParams.leveragedTokenData.debtToken,
_decodedParams.collateralAndDebtSwapData
);
address inputToken;
if(_decodedParams.paymentToken == ETH_ADDRESS) {
weth.deposit{value: _decodedParams.limitAmount}();
inputToken = address(weth);
} else {
inputToken = _decodedParams.paymentToken;
// Security Assumption: No one can manipulate Morpho such that this original sender is not the original sender of the transaction
// Alternatively:
IERC20(inputToken).safeTransferFrom(_decodedParams.originalSender, address(this), _decodedParams.limitAmount);
}
_executeSwapData(
inputToken,
_decodedParams.paymentTokenSwapData
);
emit FlashMint(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
_decodedParams.setAmount
);
}
/**
* Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
*
* @param _decodedParams Struct containing token addresses / amounts to perform redemption
*/
function _performRedemption(
DecodedParams memory _decodedParams
)
internal
{
debtIssuanceModule.redeem(_decodedParams.setToken, _decodedParams.setAmount, address(this));
if(_decodedParams.isAave) {
// Withdraw underlying collateral token from the aToken position returned by redeem step
_withdrawCollateralToken(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.leveragedTokenData.collateralAmount - ROUNDING_ERROR_MARGIN
);
}
// Swap Collateral for Debt Tokens
_executeSwapData(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.collateralAndDebtSwapData
);
// Swap Collateral tokens for Payment token
_executeSwapData(
_decodedParams.leveragedTokenData.collateralToken,
_decodedParams.paymentTokenSwapData
);
emit FlashRedeem(
_decodedParams.originalSender,
_decodedParams.setToken,
_decodedParams.paymentToken,
_decodedParams.setAmount
);
}
/**
* Returns the collateral / debt token addresses and amounts for a leveraged index
*
* @param _setToken Address of the SetToken to be issued / redeemed
* @param _setAmount Amount of SetTokens to issue / redeem
* @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
* @param _isAave Boolean indicating wether given leveraged token is based on aave leverage module (or morpho)
*
* @return Struct containing the collateral / debt token addresses and amounts
*/
function _getLeveragedTokenData(
ISetToken _setToken,
uint256 _setAmount,
bool _isIssuance,
bool _isAave
)
internal
view
returns (LeveragedTokenData memory)
{
address[] memory components;
uint256[] memory equityPositions;
uint256[] memory debtPositions;
if(_isIssuance){
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
} else {
(components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
}
require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
if(equityPositions[0] > 0){
return LeveragedTokenData(
_isAave ? IAToken(components[0]).UNDERLYING_ASSET_ADDRESS() : components[0],
_isAave ? components[0] : address(0),
equityPositions[0] + ROUNDING_ERROR_MARGIN,
components[1],
debtPositions[1]
);
} else {
return LeveragedTokenData(
_isAave ? IAToken(components[1]).UNDERLYING_ASSET_ADDRESS() : components[1],
_isAave ? components[1] : address(0),
equityPositions[1] + ROUNDING_ERROR_MARGIN,
components[0],
debtPositions[0]
);
}
}
/**
* Approves max amount of given token to all exchange routers and the debt issuance module
*
* @param _token Address of the token to be approved
*/
function _approveToken(IERC20 _token) internal {
_safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
}
/**
* Approves max amount of token to lending pool
*
* @param _token Address of the token to approve
*/
function _approveTokenToLendingPool(
IERC20 _token
)
internal
{
uint256 allowance = _token.allowance(address(this), address(aavePool));
if (allowance > 0) {
_token.approve(address(aavePool), 0);
}
_token.approve(address(aavePool), MAX_UINT256);
}
/**
* Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
* Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
*
* @param _setToken Address of the SetToken being initialized
* @param _setAmount Amount of the SetToken being initialized
* @param _inputToken Address of the input token to pay with
* @param _maxAmountInputToken Maximum amount of input token to pay
* @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
* @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
* @param _isAave Boolean indicating wether given token is based on aave or morpho leverage module
*/
function _initiateIssuance(
ISetToken _setToken,
uint256 _setAmount,
address _inputToken,
uint256 _maxAmountInputToken,
SwapData memory _swapDataDebtForCollateral,
SwapData memory _swapDataInputToken,
bool _isAave
)
internal
returns(uint256[] memory)
{
_syncLeverageModule(_isAave, _setToken);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true, _isAave);
uint256[] memory tokenBalances = _getTokenBalances(_inputToken, leveragedTokenData);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_isAave,
_setAmount,
msg.sender,
true,
_inputToken,
_maxAmountInputToken,
leveragedTokenData,
_swapDataDebtForCollateral,
_swapDataInputToken
)
);
_flashloan(leveragedTokenData.collateralToken, leveragedTokenData.collateralAmount, params);
return _returnExcessTokenBalances(tokenBalances, _inputToken, leveragedTokenData);
}
/**
* Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
*
* @param _setToken Address of the SetToken to redeem
* @param _setAmount Amount of the SetToken to redeem
* @param _outputToken Address of token to return to the user
* @param _minAmountOutputToken Minimum amount of output token to receive
* @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
* @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
* @param _isAave Boolean indicating wether given leverage token is based on aave leverage module
*/
function _initiateRedemption(
ISetToken _setToken,
uint256 _setAmount,
address _outputToken,
uint256 _minAmountOutputToken,
SwapData memory _swapDataCollateralForDebt,
SwapData memory _swapDataOutputToken,
bool _isAave
)
internal
returns(uint256[] memory returnedAmounts)
{
_syncLeverageModule(_isAave, _setToken);
_setToken.safeTransferFrom(msg.sender, address(this), _setAmount);
LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false, _isAave);
uint256[] memory tokenBalances = _getTokenBalances(_outputToken, leveragedTokenData);
bytes memory params = abi.encode(
DecodedParams(
_setToken,
_isAave,
_setAmount,
msg.sender,
false,
_outputToken,
_minAmountOutputToken,
leveragedTokenData,
_swapDataCollateralForDebt,
_swapDataOutputToken
)
);
_flashloan(leveragedTokenData.debtToken, leveragedTokenData.debtAmount, params);
returnedAmounts = _returnExcessTokenBalances(tokenBalances, _outputToken, leveragedTokenData);
require(
(_outputToken == leveragedTokenData.collateralToken && returnedAmounts[0] >= _minAmountOutputToken)
||
(_outputToken == leveragedTokenData.debtToken && returnedAmounts[1] >= _minAmountOutputToken)
||
(returnedAmounts[2] >= _minAmountOutputToken),
"INSUFFICIENT OUTPUT AMOUNT"
);
}
function _syncLeverageModule(
bool _isAave,
ISetToken _setToken
)
internal {
if(_isAave) {
aaveLeverageModule.sync(_setToken);
} else {
morphoLeverageModule.sync(_setToken);
}
}
/**
* Swaps the debt tokens obtained from issuance for the collateral
*
* @param _inputToken Input token to be approved for this swap
* @param _swapData Struct containing path and fee data for swap
*
*/
function _executeSwapData(
address _inputToken,
SwapData memory _swapData
)
internal
{
if(_swapData.swapTarget != address(0)){
IERC20(_inputToken).approve(_swapData.swapTarget, IERC20(_inputToken).balanceOf(address(this)));
_fillQuote(_swapData);
}
}
/**
* Execute a 0x Swap quote
*
* @param _quote Swap quote as returned by 0x API
*
*/
function _fillQuote(
SwapData memory _quote
)
internal
{
require(swapTargetWhitelist[_quote.swapTarget], "swapTarget not whitelisted");
(bool success, bytes memory returndata) = _quote.swapTarget.call(_quote.callData);
// Forwarding errors including new custom errors
// Taken from: https://ethereum.stackexchange.com/a/111187/73805
if (!success) {
if (returndata.length == 0) revert();
assembly {
revert(add(32, returndata), mload(returndata))
}
}
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
* @param _requiredAllowance Target allowance to set
*/
function _safeApprove(
IERC20 _token,
address _spender,
uint256 _requiredAllowance
)
internal
{
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
}
}
/**
* Triggers the flashloan from the BalancerV2 Vault
*
* @param token Address of the token to loan
* @param amount Amount to loan
* @param params Encoded memory to forward to the executeOperation method
*/
function _flashloan(
address token,
uint256 amount,
bytes memory params
)
internal
{
require(flashLoanBenefactor == address(0), "Flashloan already taken");
flashLoanBenefactor = msg.sender;
address[] memory assets = new address[](1);
assets[0] = token;
uint256[] memory amounts = new uint256[](1);
amounts[0] = amount;
balancerV2Vault.flashLoan(this, assets, amounts, params);
flashLoanBenefactor = address(0);
}
/**
* Deposit collateral to aave to obtain collateralAToken for issuance
*
* @param _collateralToken Address of collateral token
* @param _depositAmount Amount to deposit
*/
function _depositCollateralToken(
address _collateralToken,
uint256 _depositAmount
) internal {
aavePool.deposit(_collateralToken, _depositAmount, address(this), 0);
}
/**
* Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
*
* @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
* @param _collateralAmount Amount of collateralToken to withdraw
*/
function _withdrawCollateralToken(
address _collateralToken,
uint256 _collateralAmount
) internal {
aavePool.withdraw(_collateralToken, _collateralAmount, address(this));
}
receive() external payable {}
}
FlashMintNAV.sol 412 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { INAVIssuanceModule } from "../interfaces/INAVIssuanceModule.sol";
import { ISetValuer } from "../interfaces/ISetValuer.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { DEXAdapterV3 } from "./DEXAdapterV3.sol";
/**
* @title FlashMintNAV
* @author Index Cooperative
* @notice Part of a family of FlashMint contracts that allows users to issue and redeem SetTokens with a single input/output token (ETH/ERC20).
* Allows the caller to combine a DEX swap and a SetToken issuance or redemption in a single transaction.
* Supports SetTokens that use a NAV Issuance Module, and does not require use of off-chain APIs for swap quotes.
* The SetToken must be configured with a reserve asset that has liquidity on the exchanges supported by the DEXAdapterV3 library.
*
* See the FlashMint SDK for integrating any FlashMint contract (https://github.com/IndexCoop/flash-mint-sdk).
*/
contract FlashMintNAV is Ownable, ReentrancyGuard {
using DEXAdapterV3 for DEXAdapterV3.Addresses;
using Address for address payable;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Constants ============== */
// Placeholder address to identify ETH where it is treated as if it was an ERC20 token
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ Immutables ============ */
address public immutable WETH;
IController public immutable setController;
INAVIssuanceModule public immutable navIssuanceModule;
/* ============ State Variables ============ */
DEXAdapterV3.Addresses public dexAdapter;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountSetIssued, // The amount of SetTokens received by the recipient
uint256 _amountInputToken // The amount of input tokens used for issuance
);
event FlashRedeem(
address indexed _recipient, // The recipient adress of the output tokens obtained for redemption
ISetToken indexed _setToken, // The redeemed SetToken
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/**
* Initializes the contract with controller, issuance module, and DEXAdapterV3 library addresses.
*
* @param _setController Address of the protocol controller contract
* @param _navIssuanceModule NAV Issuance Module used to issue and redeem SetTokens
* @param _dexAddresses Struct containing addresses for the DEXAdapterV3 library
*/
constructor(
IController _setController,
INAVIssuanceModule _navIssuanceModule,
DEXAdapterV3.Addresses memory _dexAddresses
)
public
{
setController = _setController;
navIssuanceModule = _navIssuanceModule;
dexAdapter = _dexAddresses;
WETH = _dexAddresses.weth;
}
/* ============ External Functions ============ */
/**
* Withdraw tokens to selected address if they end up in the contract
*
* @param _tokens Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(
IERC20[] calldata _tokens,
address payable _to
) external payable onlyOwner {
for (uint256 i = 0; i < _tokens.length; i++) {
if (address(_tokens[i]) == DEXAdapterV3.ETH_ADDRESS) {
_to.sendValue(address(this).balance);
} else {
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
receive() external payable {
// required for weth.withdraw() to work properly
require(msg.sender == WETH, "FlashMint: DIRECT DEPOSITS NOT ALLOWED");
}
/**
* Runs all the necessary approval functions required before issuing or redeeming
* a SetToken through the NAV Issuance Module. This function needs to be called
* before this smart contract is used with any particular SetToken, and again
* whenever a new reserve asset is added.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external {
address[] memory reserveAssets = navIssuanceModule.getReserveAssets(address(_setToken));
for (uint256 i = 0; i < reserveAssets.length; i++) {
_safeApprove(IERC20(reserveAssets[i]), address(navIssuanceModule), type(uint256).max);
}
_safeApprove(IERC20(_setToken), address(navIssuanceModule), type(uint256).max);
}
/**
* Gets the amount of Set Token expected to be issued given an exact quantity of input token.
* This function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter contract
*
* @param _setToken Address of the Set Token to be issued
* @param _inputToken Address of the token used to pay for issuance. Use WETH address if input token is ETH.
* @param _inputTokenAmount Exact amount of input token to spend
* @param _reserveAssetSwapData Swap data to trade input token for reserve asset. Use empty swap data if input token is the reserve asset.
*
* @return Amount of SetTokens expected to be issued
*/
function getIssueAmount(
ISetToken _setToken,
address _inputToken,
uint256 _inputTokenAmount,
DEXAdapterV3.SwapData memory _reserveAssetSwapData
)
external
returns (uint256)
{
address reserveAsset = _getAndValidateReserveAsset(_setToken, _inputToken, _reserveAssetSwapData.path, true);
uint256 reserveAssetReceived = dexAdapter.getAmountOut(_reserveAssetSwapData, _inputTokenAmount);
return navIssuanceModule.getExpectedSetTokenIssueQuantity(
_setToken,
reserveAsset,
reserveAssetReceived
);
}
/**
* Gets the amount of output token expected to be received after redeeming a given quantity of Set Token
* for reserve asset and then swapping the reserve asset for output token.
* This function is not marked view, but should be static called from frontends.
* This constraint is due to the need to interact with the Uniswap V3 quoter contract
*
* @param _setToken Address of the Set Token to be redeemed
* @param _setTokenAmount Amount of Set Token to be redeemed
* @param _outputToken Address of the token to send to caller after redemption
* @param _reserveAssetSwapData Swap data to trade reserve asset for output token
*
* @return Amount of output tokens expected to be sent to caller
*/
function getRedeemAmountOut(
ISetToken _setToken,
uint256 _setTokenAmount,
address _outputToken,
DEXAdapterV3.SwapData memory _reserveAssetSwapData
)
external
returns (uint256)
{
address reserveAsset = _getAndValidateReserveAsset(_setToken, _outputToken, _reserveAssetSwapData.path, false);
uint256 reserveAssetReceived = navIssuanceModule.getExpectedReserveRedeemQuantity(
_setToken,
reserveAsset,
_setTokenAmount
);
return dexAdapter.getAmountOut(_reserveAssetSwapData, reserveAssetReceived);
}
/**
* Issues a minimum amount of SetTokens for an exact amount of ETH.
*
* @param _setToken Address of the SetToken to be issued
* @param _minSetTokenAmount Minimum amount of SetTokens to be issued
* @param _reserveAssetSwapData Swap data to trade WETH for reserve asset
*/
function issueSetFromExactETH(
ISetToken _setToken,
uint256 _minSetTokenAmount,
DEXAdapterV3.SwapData memory _reserveAssetSwapData
)
external
payable
nonReentrant
{
require(msg.value > 0, "FlashMint: NO ETH SENT");
IWETH(WETH).deposit{value: msg.value}();
address reserveAsset = _getAndValidateReserveAsset(_setToken, WETH, _reserveAssetSwapData.path, true);
uint256 reserveAssetReceived = dexAdapter.swapExactTokensForTokens(msg.value, 0, _reserveAssetSwapData);
uint256 setTokenBalanceBefore = _setToken.balanceOf(msg.sender);
navIssuanceModule.issue(
_setToken,
reserveAsset,
reserveAssetReceived,
_minSetTokenAmount,
msg.sender
);
uint256 setTokenIssued = _setToken.balanceOf(msg.sender).sub(setTokenBalanceBefore);
emit FlashMint(msg.sender, _setToken, IERC20(ETH_ADDRESS), setTokenIssued, msg.value);
}
/**
* Issues a minimum amount of SetTokens for an exact amount of ERC20.
*
* @param _setToken Address of the SetToken to issue
* @param _minSetTokenAmount Minimum amount of SetTokens to issue
* @param _inputToken Address of token used to pay for issuance
* @param _inputTokenAmount Amount of input token to spend
* @param _reserveAssetSwapData Swap data to trade input token for reserve asset. Can use empty swap data if input token is reserve asset.
*/
function issueSetFromExactERC20(
ISetToken _setToken,
uint256 _minSetTokenAmount,
IERC20 _inputToken,
uint256 _inputTokenAmount,
DEXAdapterV3.SwapData memory _reserveAssetSwapData
)
external
nonReentrant
{
address reserveAsset = _getAndValidateReserveAsset(_setToken, address(_inputToken), _reserveAssetSwapData.path, true);
_inputToken.safeTransferFrom(msg.sender, address(this), _inputTokenAmount);
uint256 reserveAssetReceived = dexAdapter.swapExactTokensForTokens(_inputTokenAmount, 0, _reserveAssetSwapData);
uint256 setTokenBalanceBefore = _setToken.balanceOf(msg.sender);
navIssuanceModule.issue(
_setToken,
reserveAsset,
reserveAssetReceived,
_minSetTokenAmount,
msg.sender
);
uint256 setTokenIssued = _setToken.balanceOf(msg.sender).sub(setTokenBalanceBefore);
emit FlashMint(msg.sender, _setToken, IERC20(ETH_ADDRESS), setTokenIssued, _inputTokenAmount);
}
/**
* Redeems an exact amount of SetTokens for ETH.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken to redeem
* @param _setTokenAmount Amount of SetTokens to redeem
* @param _minEthAmount Minimum amount of ETH to be received by caller
* @param _reserveAssetSwapData Swap data to trade reserve asset for WETH
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _setTokenAmount,
uint256 _minEthAmount,
DEXAdapterV3.SwapData memory _reserveAssetSwapData
)
external
nonReentrant
{
address reserveAsset = _getAndValidateReserveAsset(_setToken, WETH, _reserveAssetSwapData.path, false);
uint256 reserveAssetBalanceBefore = IERC20(reserveAsset).balanceOf(address(this));
_setToken.safeTransferFrom(msg.sender, address(this), _setTokenAmount);
navIssuanceModule.redeem(
_setToken,
reserveAsset,
_setTokenAmount,
0,
address(this)
);
uint256 reserveAssetReceived = IERC20(reserveAsset).balanceOf(address(this)).sub(reserveAssetBalanceBefore);
uint256 wethReceived = dexAdapter.swapExactTokensForTokens(reserveAssetReceived, 0, _reserveAssetSwapData);
require(wethReceived >= _minEthAmount, "FlashMint: NOT ENOUGH ETH RECEIVED");
IWETH(WETH).withdraw(wethReceived);
payable(msg.sender).sendValue(wethReceived);
emit FlashRedeem(msg.sender, _setToken, IERC20(ETH_ADDRESS), _setTokenAmount, wethReceived);
}
/**
* Redeems an exact amount of SetTokens for ETH.
* The SetToken must be approved by the sender to this contract.
*
* @param _setToken Address of the SetToken to redeem
* @param _setTokenAmount Amount of SetTokens to redeem
* @param _outputToken Address of the token to be received by caller
* @param _minOutputTokenAmount Minimum amount of output token to be received by caller
* @param _reserveAssetSwapData Swap data to trade reserve asset for output token. Can use empty swap data if output token is reserve asset.
*/
function redeemExactSetForERC20(
ISetToken _setToken,
uint256 _setTokenAmount,
IERC20 _outputToken,
uint256 _minOutputTokenAmount,
DEXAdapterV3.SwapData memory _reserveAssetSwapData
)
external
nonReentrant
{
address reserveAsset = _getAndValidateReserveAsset(_setToken, address(_outputToken), _reserveAssetSwapData.path, false);
uint256 reserveAssetBalanceBefore = IERC20(reserveAsset).balanceOf(address(this));
_setToken.safeTransferFrom(msg.sender, address(this), _setTokenAmount);
navIssuanceModule.redeem(
_setToken,
reserveAsset,
_setTokenAmount,
0,
address(this)
);
uint256 reserveAssetReceived = IERC20(reserveAsset).balanceOf(address(this)).sub(reserveAssetBalanceBefore);
uint256 outputTokenReceived = dexAdapter.swapExactTokensForTokens(reserveAssetReceived, 0, _reserveAssetSwapData);
require(outputTokenReceived >= _minOutputTokenAmount, "FlashMint: NOT ENOUGH OUTPUT TOKEN RECEIVED");
_outputToken.safeTransfer(msg.sender, outputTokenReceived);
emit FlashRedeem(msg.sender, _setToken, _outputToken, _setTokenAmount, outputTokenReceived);
}
/* ============ Internal Functions ============ */
/**
* Validates the reserve asset and swap path for issuance or redemption.
*
* @param _setToken Address of SetToken being issued or redeemed
* @param _paymentToken Address of input token if issuance, or output token if redemption
* @param _path Array of token addresses representing the swap path
* @param _isIssuance bool indicating issuance or redemption
*/
function _getAndValidateReserveAsset(
ISetToken _setToken,
address _paymentToken,
address[] memory _path,
bool _isIssuance
)
internal
view
returns(address)
{
address reserveAsset;
if (_path.length > 0) {
if (_isIssuance) {
require(_path[0] == _paymentToken, "FLASHMINT: FIRST ADDRESS IN SWAP PATH MUST BE INPUT TOKEN");
reserveAsset = _path[_path.length - 1];
} else {
require(_path[_path.length - 1] == address(_paymentToken), "FLASHMINT: LAST ADDRESS IN SWAP PATH MUST BE OUTPUT TOKEN");
reserveAsset = _path[0];
}
} else {
reserveAsset = address(_paymentToken);
}
require(navIssuanceModule.isReserveAsset(_setToken, reserveAsset), "FLASHMINT: INVALID RESERVE ASSET");
return reserveAsset;
}
/**
* Sets a max approval limit for an ERC20 token, provided the current allowance
* is less than the required allownce.
*
* @param _token Token to approve
* @param _spender Spender address to approve
*/
function _safeApprove(IERC20 _token, address _spender, uint256 _requiredAllowance) internal {
uint256 allowance = _token.allowance(address(this), _spender);
if (allowance < _requiredAllowance) {
_token.safeIncreaseAllowance(_spender, type(uint256).max - allowance);
}
}
}
FlashMintNotional.sol 917 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IBasicIssuanceModule } from "../interfaces/IBasicIssuanceModule.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { INotionalTradeModule } from "../interfaces/INotionalTradeModule.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWrappedfCash } from "../interfaces/IWrappedfCash.sol";
import { IWrappedfCashFactory } from "../interfaces/IWrappedfCashFactory.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { DEXAdapter } from "./DEXAdapter.sol";
/**
* @title FlashMintNotional
* @author Index Coop
*
* Contract for issuing and redeeming a Set Token that contains wrappedfCash position
* Includes (in the issuance case):
* - Matching fCash components to their underlying asset
* - Swapping input token to underlying asset
* - Minting fCash positions from underlying asset
* - Issuing set token
*/
contract FlashMintNotional is Ownable, ReentrancyGuard {
using Address for address payable;
using Address for address;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
using DEXAdapter for DEXAdapter.Addresses;
struct TradeData {
ISetToken setToken;
uint256 amountSetToken;
IERC20 paymentToken;
uint256 limitAmount;
address issuanceModule;
bool isDebtIssuance;
uint256 slippage;
bool redeemMaturedPositions;
}
/* ============ Constants ============== */
// Placeholder address to identify ETH where it is treated as if it was an ERC20 token
address constant public ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ State Variables ============ */
IController public immutable setController;
IWrappedfCashFactory public immutable wrappedfCashFactory;
INotionalTradeModule public immutable notionalTradeModule;
DEXAdapter.Addresses public addresses;
uint256 public decodedIdGasLimit;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the issued SetTokens
ISetToken indexed _setToken, // The issued SetToken
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
uint256 _amountInputToken, // The amount of input tokens used for issuance
uint256 _amountSetIssued // The amount of SetTokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient adress of the output tokens obtained for redemption
ISetToken indexed _setToken, // The redeemed SetToken
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
modifier isValidModule(address _issuanceModule) {
require(setController.isModule(_issuanceModule), "FlashMint: INVALID ISSUANCE MODULE");
_;
}
/**
* Sets various contract addresses and gas limit for getDecodedId call
*
* @param _weth Address of wrapped native token
* @param _setController SetToken controller used to verify a given token is a set
* @param _wrappedfCashFactory Factory contract creating new fCash wrappers
* @param _notionalTradeModule Module used to make sure matured positions are redeemed
* @param _quickRouter Address of quickswap router
* @param _sushiRouter Address of sushiswap router
* @param _uniV3Router Address of uniswap v3 router
* @param _uniV3Quoter Address of uniswap v3 quoter
* @param _curveAddressProvider Contract to get current implementation address of curve registry
* @param _curveCalculator Contract to calculate required input to receive given output in curve (for exact output swaps)
* @param _decodedIdGasLimit Gas limit for call to getDecodedID
*/
constructor(
address _weth,
IController _setController,
IWrappedfCashFactory _wrappedfCashFactory,
INotionalTradeModule _notionalTradeModule,
address _quickRouter,
address _sushiRouter,
address _uniV3Router,
address _uniV3Quoter,
address _curveAddressProvider,
address _curveCalculator,
uint256 _decodedIdGasLimit
)
public
{
setController = _setController;
wrappedfCashFactory = _wrappedfCashFactory;
notionalTradeModule = _notionalTradeModule;
addresses.weth = _weth;
addresses.quickRouter = _quickRouter;
addresses.sushiRouter = _sushiRouter;
addresses.uniV3Router = _uniV3Router;
addresses.uniV3Quoter = _uniV3Quoter;
addresses.curveAddressProvider = _curveAddressProvider;
addresses.curveCalculator = _curveCalculator;
decodedIdGasLimit = _decodedIdGasLimit;
}
/* ============ Public Functions ============ */
/**
* Returns component positions required for issuance
*
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
* @param _setToken Set token to issue
* @param _amountSetToken Amount of set token to issue
*/
function getRequiredIssuanceComponents(address _issuanceModule, bool _isDebtIssuance, ISetToken _setToken, uint256 _amountSetToken) public view returns(address[] memory components, uint256[] memory positions) {
if(_isDebtIssuance) {
(components, positions, ) = IDebtIssuanceModule(_issuanceModule).getRequiredComponentIssuanceUnits(_setToken, _amountSetToken);
}
else {
(components, positions) = IBasicIssuanceModule(_issuanceModule).getRequiredComponentUnitsForIssue(_setToken, _amountSetToken);
}
}
/**
* Returns component positions required for Redemption
*
* @param _issuanceModule Address of issuance Module to use
* @param _isDebtIssuance Flag indicating wether given issuance module is a debt issuance module
* @param _setToken Set token to issue
* @param _amountSetToken Amount of set token to issue
*/
function getRequiredRedemptionComponents(address _issuanceModule, bool _isDebtIssuance, ISetToken _setToken, uint256 _amountSetToken) public view returns(address[] memory components, uint256[] memory positions) {
if(_isDebtIssuance) {
(components, positions, ) = IDebtIssuanceModule(_issuanceModule).getRequiredComponentRedemptionUnits(_setToken, _amountSetToken);
}
else {
components = _setToken.getComponents();
positions = new uint256[](components.length);
for(uint256 i = 0; i < components.length; i++) {
uint256 unit = uint256(_setToken.getDefaultPositionRealUnit(components[i]));
positions[i] = unit.preciseMul(_amountSetToken);
}
}
}
/* ============ External Functions ============ */
/**
* @dev Update gas limit of call to getDecodedID in _isWrappedFCash
* @param _decodedIdGasLimit New gas limit for call to getDecodedID
*/
function updateDecodedIdGasLimit(uint256 _decodedIdGasLimit) external onlyOwner {
require(_decodedIdGasLimit != 0, "DecodedIdGasLimit cannot be zero");
decodedIdGasLimit = _decodedIdGasLimit;
}
/**
* Withdraw slippage to selected address
*
* @param _tokens Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(IERC20[] calldata _tokens, address payable _to) external onlyOwner payable {
for(uint256 i = 0; i < _tokens.length; i++) {
if(address(_tokens[i]) == ETH_ADDRESS){
_to.sendValue(address(this).balance);
}
else{
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
/**
* Returns components and units but replaces wrappefCash positions with the corresponding amount of underlying token needed to mint
*
* @param _setToken Address of the set token to redeem
* @param _amountSetToken Amount of set token to redeem
* @param _issuanceModule Address of the issuance module to use for getting raw list of components and units
* @param _isDebtIssuance Boolean indicating wether given issuance module is an instance of Debt- or BasicIssuanceModule
* @param _slippage Relative slippage (with 18 decimals) to subtract from wrappedfCash's estimated redemption amount to allow for approximation error
*/
function getFilteredComponentsRedemption(
ISetToken _setToken,
uint256 _amountSetToken,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage
)
external
view
returns (address[] memory filteredComponents, uint[] memory filteredUnits)
{
return _getFilteredComponentsRedemption(_setToken, _amountSetToken, _issuanceModule, _isDebtIssuance, _slippage);
}
/**
* Returns filtered components after redeeming matured positions
* THIS METHOD SHOULD ONLY BE CALLED WITH STATICCALL
*
* @param _setToken Address of the set token to redeem
* @param _amountSetToken Amount of set token to redeem
* @param _issuanceModule Address of the issuance module to use for getting raw list of components and units
* @param _isDebtIssuance Boolean indicating wether given issuance module is an instance of Debt- or BasicIssuanceModule
* @param _slippage Relative slippage (with 18 decimals) to subtract from wrappedfCash's estimated redemption amount to allow for approximation error
*/
function getFilteredComponentsRedemptionAfterMaturityRedemption(
ISetToken _setToken,
uint256 _amountSetToken,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage
)
external
returns (address[] memory filteredComponents, uint[] memory filteredUnits)
{
notionalTradeModule.redeemMaturedPositions(_setToken);
return _getFilteredComponentsRedemption(_setToken, _amountSetToken, _issuanceModule, _isDebtIssuance, _slippage);
}
/**
* Returns components and units but replaces wrappefCash positions with the corresponding amount of underlying token needed to mint
*
* @param _setToken Address of the set token to issue
* @param _amountSetToken Amount of set token to issue
* @param _issuanceModule Address of the issuance module to use for getting raw list of components and units
* @param _isDebtIssuance Boolean indicating wether given issuance module is an instance of Debt- or BasicIssuanceModule
* @param _slippage Relative slippage (with 18 decimals) to add to wrappedfCash's estimated issuance cost to allow for approximation error
*/
function getFilteredComponentsIssuance(
ISetToken _setToken,
uint256 _amountSetToken,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage
)
external
view
returns (address[] memory filteredComponents, uint[] memory filteredUnits)
{
(filteredComponents, filteredUnits, ) = _getFilteredComponentsIssuance(_setToken, _amountSetToken, _issuanceModule, _isDebtIssuance, _slippage);
}
/**
* Returns filtered components after redeeming matured positions
* THIS METHOD SHOULD ONLY BE CALLED WITH STATICCALL
*
* @param _setToken Address of the set token to issue
* @param _amountSetToken Amount of set token to issue
* @param _issuanceModule Address of the issuance module to use for getting raw list of components and units
* @param _isDebtIssuance Boolean indicating wether given issuance module is an instance of Debt- or BasicIssuanceModule
* @param _slippage Relative slippage (with 18 decimals) to add to wrappedfCash's estimated issuance cost to allow for approximation error
*/
function getFilteredComponentsIssuanceAfterMaturityRedemption(
ISetToken _setToken,
uint256 _amountSetToken,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage
)
external
returns (address[] memory filteredComponents, uint[] memory filteredUnits)
{
notionalTradeModule.redeemMaturedPositions(_setToken);
(filteredComponents, filteredUnits, ) = _getFilteredComponentsIssuance(_setToken, _amountSetToken, _issuanceModule, _isDebtIssuance, _slippage);
}
/**
* Issue set token for ETH
*
* @param _setToken Address of the set token to issue
* @param _amountSetToken Amount of set token to issue
* @param _swapData Swap data for each element of the filtered components in the same order as returned by getFilteredComponentsIssuance
* @param _issuanceModule Address of the issuance module to use for getting raw list of components and units
* @param _isDebtIssuance Boolean indicating wether given issuance module is an instance of Debt- or BasicIssuanceModule
* @param _slippage Relative slippage (with 18 decimals) to add to wrappedfCash's estimated issuance cost to allow for approximation error
* @param _redeemMaturedPositions Set to false to skip redeeming matured positions and save gas, wich will fail if there are any matured positions
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _amountSetToken,
DEXAdapter.SwapData[] memory _swapData,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage,
bool _redeemMaturedPositions
)
isValidModule(_issuanceModule)
external
payable
nonReentrant
returns (uint256)
{
IWETH(addresses.weth).deposit{value: msg.value}();
TradeData memory tradeData = TradeData(
_setToken,
_amountSetToken,
IERC20(addresses.weth),
msg.value,
_issuanceModule,
_isDebtIssuance,
_slippage,
_redeemMaturedPositions
);
uint256 totalInputTokenSpent = _issueExactSetFromToken(tradeData, _swapData);
uint256 amountTokenReturn = msg.value.sub(totalInputTokenSpent);
if (amountTokenReturn > 0) {
IWETH(addresses.weth).withdraw(amountTokenReturn);
payable(msg.sender).transfer(amountTokenReturn);
}
return totalInputTokenSpent;
}
/**
* Issue set token for ERC20 Token
*
* @param _setToken Address of the set token to issue
* @param _amountSetToken Amount of set token to issue
* @param _inputToken Address of the input token to spent
* @param _maxAmountInputToken Maximum amount of input token to spent
* @param _swapData Configuration of swaps from input token to each element of the filtered components in the same order as returned by getFilteredComponentsIssuance
* @param _issuanceModule Address of the issuance module to use for getting raw list of components and units
* @param _isDebtIssuance Boolean indicating wether given issuance module is an instance of Debt- or BasicIssuanceModule
* @param _slippage Relative slippage (with 18 decimals) to add to wrappedfCash's estimated issuance cost to allow for approximation error
* @param _redeemMaturedPositions Set to false to skip redeeming matured positions and save gas, wich will fail if there are any matured positions
*/
function issueExactSetFromToken(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData[] memory _swapData,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage,
bool _redeemMaturedPositions
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
_inputToken.safeTransferFrom(msg.sender, address(this), _maxAmountInputToken);
TradeData memory tradeData = TradeData(
_setToken,
_amountSetToken,
_inputToken,
_maxAmountInputToken,
_issuanceModule,
_isDebtIssuance,
_slippage,
_redeemMaturedPositions
);
uint256 totalInputTokenSpent = _issueExactSetFromToken(tradeData, _swapData);
_returnExcessInputToken(_inputToken, _maxAmountInputToken, totalInputTokenSpent);
return totalInputTokenSpent;
}
/**
* Redeem set token for selected output token
*
* @param _setToken Address of the set token to redeem
* @param _amountSetToken Amount of set token to redeem
* @param _outputToken Address of the output token to spent
* @param _minOutputReceive Minimum amount of output token to receive
* @param _swapData Configuration of swaps from each element of the filtered components to the output token in the same order as returned by getFilteredComponentsIssuance
* @param _issuanceModule Address of the issuance module to use for getting raw list of components and units
* @param _isDebtIssuance Boolean indicating wether given issuance module is an instance of Debt- or BasicIssuanceModule
* @param _slippage Relative slippage (with 18 decimals) to subtract from wrappedfCash's estimated redemption amount to allow for approximation error
* @param _redeemMaturedPositions Set to false to skip redeeming matured positions and save gas, wich will fail if there are any matured positions
*/
function redeemExactSetForToken(
ISetToken _setToken,
IERC20 _outputToken,
uint256 _amountSetToken,
uint256 _minOutputReceive,
DEXAdapter.SwapData[] memory _swapData,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage,
bool _redeemMaturedPositions
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
TradeData memory tradeData = TradeData(
_setToken,
_amountSetToken,
_outputToken,
_minOutputReceive,
_issuanceModule,
_isDebtIssuance,
_slippage,
_redeemMaturedPositions
);
uint256 outputAmount = _redeemExactSetForToken(tradeData, _swapData);
_outputToken.safeTransfer(msg.sender, outputAmount);
return outputAmount;
}
/**
* Redeem set token for eth
*
* @param _setToken Address of the set token to redeem
* @param _amountSetToken Amount of set token to redeem
* @param _minOutputReceive Minimum amount of output token to receive
* @param _swapData Configuration of swaps from each element of the filtered components to the output token in the same order as returned by getFilteredComponentsIssuance
* @param _issuanceModule Address of the issuance module to use for getting raw list of components and units
* @param _isDebtIssuance Boolean indicating wether given issuance module is an instance of Debt- or BasicIssuanceModule
* @param _slippage Relative slippage (with 18 decimals) to subtract from wrappedfCash's estimated redemption amount to allow for approximation error
* @param _redeemMaturedPositions Set to false to skip redeeming matured positions and save gas, wich will fail if there are any matured positions
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minOutputReceive,
DEXAdapter.SwapData[] memory _swapData,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage,
bool _redeemMaturedPositions
)
isValidModule(_issuanceModule)
external
nonReentrant
returns (uint256)
{
TradeData memory tradeData = TradeData(
_setToken,
_amountSetToken,
IERC20(addresses.weth),
_minOutputReceive,
_issuanceModule,
_isDebtIssuance,
_slippage,
_redeemMaturedPositions
);
uint256 outputAmount = _redeemExactSetForToken(tradeData, _swapData);
IWETH(addresses.weth).withdraw(outputAmount);
payable(msg.sender).transfer(outputAmount);
return outputAmount;
}
/* ============ Internal Functions ============ */
/**
* Transfer in input token, swap for components, mint fCash positions and issue set
*/
function _issueExactSetFromToken(
TradeData memory _tradeData,
DEXAdapter.SwapData[] memory _swapData
)
internal
returns (uint256)
{
uint256 inputTokenBalanceBefore = _tradeData.paymentToken.balanceOf(address(this));
if(_tradeData.redeemMaturedPositions) {
notionalTradeModule.redeemMaturedPositions(_tradeData.setToken);
}
(address[] memory componentsBought, uint256[] memory amountsBought, uint256[] memory mappingToFilteredComponents) = _buyComponentsForInputToken(
_tradeData,
_swapData
);
_mintWrappedFCashPositions(
_tradeData,
componentsBought,
amountsBought,
mappingToFilteredComponents
);
IBasicIssuanceModule(_tradeData.issuanceModule).issue(_tradeData.setToken, _tradeData.amountSetToken, msg.sender);
require(inputTokenBalanceBefore.sub(_tradeData.paymentToken.balanceOf(address(this))) <= _tradeData.limitAmount, "FlashMint: OVERSPENT");
emit FlashMint(msg.sender, _tradeData.setToken, _tradeData.paymentToken, _tradeData.limitAmount, _tradeData.amountSetToken);
return inputTokenBalanceBefore.sub(_tradeData.paymentToken.balanceOf(address(this)));
}
/**
* Redeem set, redeem fCash components, sell received tokens for output token and transfer proceds to the caller
*/
function _redeemExactSetForToken(
TradeData memory _tradeData,
DEXAdapter.SwapData[] memory _swapData
)
internal
returns (uint256)
{
uint256 outputTokenBalanceBefore = _tradeData.paymentToken.balanceOf(address(this));
if(_tradeData.redeemMaturedPositions) {
notionalTradeModule.redeemMaturedPositions(_tradeData.setToken);
}
_redeemExactSet(_tradeData.setToken, _tradeData.amountSetToken, _tradeData.issuanceModule);
_redeemWrappedFCashPositions(_tradeData);
_sellComponentsForOutputToken(_tradeData, _swapData);
uint256 outputAmount = _tradeData.paymentToken.balanceOf(address(this)).sub(outputTokenBalanceBefore);
require(outputAmount >= _tradeData.limitAmount, "FlashMint: UNDERBOUGHT");
// Emit event
emit FlashRedeem(msg.sender, _tradeData.setToken, _tradeData.paymentToken, _tradeData.amountSetToken, outputAmount);
// Return output amount
return outputAmount;
}
/**
* Sells all components (after redemption of fCash positions) for the output token
*/
function _sellComponentsForOutputToken(
TradeData memory _tradeData,
DEXAdapter.SwapData[] memory _swapData
)
internal
{
(address[] memory components, uint256[] memory componentUnits) = _getFilteredComponentsRedemption(
_tradeData.setToken,
_tradeData.amountSetToken,
_tradeData.issuanceModule,
_tradeData.isDebtIssuance,
_tradeData.slippage
);
require(components.length == _swapData.length, "Components / Swapdata mismatch");
for (uint256 i = 0; i < components.length; i++) {
uint256 maxAmountSell = componentUnits[i];
address component = components[i];
// Component Address being zero means the filtered list is finished and all remaining components are 0 as well
if(component == address(0)){
break;
}
// If the component is equal to the output token we don't have to trade
if(component != address(_tradeData.paymentToken)) {
addresses.swapExactTokensForTokens(maxAmountSell, 0, _swapData[i]);
}
}
}
/**
* Redeem all fCash positions for the underlying token
*/
function _redeemWrappedFCashPositions(
TradeData memory _tradeData
)
internal
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredRedemptionComponents(
_tradeData.issuanceModule,
_tradeData.isDebtIssuance,
_tradeData.setToken,
_tradeData.amountSetToken
);
for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
uint256 units = componentUnits[i];
if(_isWrappedFCash(component)) {
IWrappedfCash(component).redeemToUnderlying(units, address(this), 0);
}
}
}
/**
* Mint all fCash positions from the underlying token
*/
function _mintWrappedFCashPositions(
TradeData memory _tradeData,
address[] memory componentsBought,
uint256[] memory amountsAvailable,
uint256[] memory mappingToFilteredComponents
)
internal
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredIssuanceComponents(
_tradeData.issuanceModule,
_tradeData.isDebtIssuance,
_tradeData.setToken,
_tradeData.amountSetToken
);
for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
uint256 units = componentUnits[i];
if(_isWrappedFCash(component)) {
IERC20 underlyingToken = _getUnderlyingToken(IWrappedfCash(component));
uint256 componentIndex = mappingToFilteredComponents[i];
uint256 amountAvailable = amountsAvailable[componentIndex];
underlyingToken.safeApprove(component, amountAvailable);
uint256 underlyingBalanceBefore = underlyingToken.balanceOf(address(this));
IWrappedfCash(component).mintViaUnderlying(amountAvailable, uint88(units), address(this), 0);
uint256 amountSpent = underlyingBalanceBefore.sub(underlyingToken.balanceOf(address(this)));
amountsAvailable[componentIndex] = amountsAvailable[componentIndex].sub(amountSpent);
}
IERC20(component).safeApprove(_tradeData.issuanceModule, units);
}
}
/**
* Get underlying token of fCash positions returning weth address in case underlying is eth
*/
function _getUnderlyingToken(
IWrappedfCash _wrappedfCash
)
internal
view
returns(IERC20)
{
(IERC20 underlyingToken, bool isEth) = _wrappedfCash.getToken(true);
if(isEth) {
underlyingToken = IERC20(addresses.weth);
}
return underlyingToken;
}
/**
* Transfers given amount of set token from the sender and redeems it for underlying components.
* Obtained component tokens are sent to this contract.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amount Amount of SetToken to be redeemed
*/
function _redeemExactSet(ISetToken _setToken, uint256 _amount, address _issuanceModule) internal returns (uint256) {
_setToken.safeTransferFrom(msg.sender, address(this), _amount);
_setToken.safeApprove(_issuanceModule, _amount);
IBasicIssuanceModule(_issuanceModule).redeem(_setToken, _amount, address(this));
}
/**
* Returns excess input token
*
* @param _inputToken Address of the input token to return
* @param _receivedAmount Amount received by the caller
* @param _spentAmount Amount spent for issuance
*/
function _returnExcessInputToken(IERC20 _inputToken, uint256 _receivedAmount, uint256 _spentAmount) internal {
uint256 amountTokenReturn = _receivedAmount.sub(_spentAmount);
if (amountTokenReturn > 0) {
_inputToken.safeTransfer(msg.sender, amountTokenReturn);
}
}
/**
* @dev Checks if a given address is an fCash position that was deployed from the factory
*/
function _isWrappedFCash(address _fCashPosition) internal view returns(bool){
if(!_fCashPosition.isContract()) {
return false;
}
//Had to add this gas limit since this call wasted all the gas when directed to WETH in unittests
try IWrappedfCash(_fCashPosition).getDecodedID{gas: decodedIdGasLimit}() returns(uint16 _currencyId, uint40 _maturity){
try wrappedfCashFactory.computeAddress(_currencyId, _maturity) returns(address _computedAddress){
return _fCashPosition == _computedAddress;
} catch {
return false;
}
} catch {
return false;
}
}
/**
* @dev Returns estimated amount of underlying tokens spent on minting given amount of fCash, adding given slippage percentage
*/
function _getUnderlyingTokensForMint(IWrappedfCash _fCashPosition, uint256 _fCashAmount, uint256 _slippage)
internal
view
returns(uint256)
{
return _fCashPosition.previewMint(_fCashAmount).mul(1 ether + _slippage).div(1 ether);
}
/**
* @dev Returns estimated amount of underlying tokens returned on redeeming given amount of fCash, subtracting given slippage percentage
*/
function _getUnderlyingTokensForRedeem(IWrappedfCash _fCashPosition, uint256 _fCashAmount, uint256 _slippage)
internal
view
returns(uint256)
{
return _fCashPosition.previewRedeem(_fCashAmount).mul(1 ether - _slippage).div(1 ether);
}
/**
* @dev Helper method to find a given address in the list. Returns index+1 if found, 0 if not found.
*/
function _findComponent(address[] memory _components, address _toFind)
internal
pure
returns(uint256)
{
for(uint256 i = 0; i < _components.length; i++) {
if(_components[i] == _toFind){
return i + 1;
}
}
return 0;
}
/**
* @dev Swaps input token for required amounts of filtered components
*/
function _buyComponentsForInputToken(
TradeData memory _tradeData,
DEXAdapter.SwapData[] memory _swapData
)
internal
returns(address[] memory, uint256[] memory, uint256[] memory)
{
(address[] memory components, uint256[] memory componentUnits, uint256[] memory mappingToFilteredComponents) = _getFilteredComponentsIssuance(
_tradeData.setToken,
_tradeData.amountSetToken,
_tradeData.issuanceModule,
_tradeData.isDebtIssuance,
_tradeData.slippage
);
require(components.length == _swapData.length, "Components / Swapdata mismatch");
uint256[] memory boughtAmounts = new uint256[](components.length);
for (uint256 i = 0; i < components.length; i++) {
if(components[i] == address(0)){
break;
}
// If the component is equal to the input token we don't have to trade
if(components[i] != address(_tradeData.paymentToken)) {
uint256 componentBalanceBefore = IERC20(components[i]).balanceOf(address(this));
addresses.swapTokensForExactTokens(componentUnits[i], _tradeData.limitAmount, _swapData[i]);
boughtAmounts[i] = IERC20(components[i]).balanceOf(address(this)).sub(componentBalanceBefore);
} else {
boughtAmounts[i] = componentUnits[i];
}
}
return(components, boughtAmounts, mappingToFilteredComponents);
}
/**
* @dev Returns expected of components received upon set tokens redemption, replacing fCash position with equivalent amount of underlying token
*/
function _getFilteredComponentsRedemption(
ISetToken _setToken,
uint256 _amountSetToken,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage
)
internal
view
returns (address[] memory filteredComponents, uint[] memory filteredUnits)
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredRedemptionComponents(_issuanceModule, _isDebtIssuance, _setToken, _amountSetToken);
filteredComponents = new address[](components.length);
filteredUnits = new uint256[](components.length);
uint j = 0;
for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
uint256 units;
if(_isWrappedFCash(component)) {
units = _getUnderlyingTokensForRedeem(IWrappedfCash(component), componentUnits[i], _slippage);
IERC20 underlyingToken = _getUnderlyingToken(IWrappedfCash(component));
component = address(underlyingToken);
}
else {
units = componentUnits[i];
}
uint256 componentIndex = _findComponent(filteredComponents, component);
if(componentIndex > 0){
filteredUnits[componentIndex - 1] = filteredUnits[componentIndex - 1].add(units);
} else {
filteredComponents[j] = component;
filteredUnits[j] = units;
j++;
}
}
}
/**
* @dev Returns expected of components spent upon set tokens issuance, replacing fCash position with equivalent amount of underlying token
*/
function _getFilteredComponentsIssuance(
ISetToken _setToken,
uint256 _amountSetToken,
address _issuanceModule,
bool _isDebtIssuance,
uint256 _slippage
)
internal
view
returns (address[] memory filteredComponents, uint[] memory filteredUnits, uint256[] memory mappingToFilteredComponent)
{
(address[] memory components, uint256[] memory componentUnits) = getRequiredIssuanceComponents(_issuanceModule, _isDebtIssuance, _setToken, _amountSetToken);
filteredComponents = new address[](components.length);
filteredUnits = new uint256[](components.length);
mappingToFilteredComponent = new uint256[](components.length);
uint j = 0;
for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
uint256 units;
if(_isWrappedFCash(component)) {
units = _getUnderlyingTokensForMint(IWrappedfCash(component), componentUnits[i], _slippage);
IERC20 underlyingToken = _getUnderlyingToken(IWrappedfCash(component));
component = address(underlyingToken);
}
else {
units = componentUnits[i];
}
uint256 componentIndex = _findComponent(filteredComponents, component);
if(componentIndex > 0){
filteredUnits[componentIndex - 1] = filteredUnits[componentIndex - 1].add(units);
mappingToFilteredComponent[i] = componentIndex - 1;
} else {
filteredComponents[j] = component;
filteredUnits[j] = units;
mappingToFilteredComponent[i] = j;
j++;
}
}
}
/**
* @dev Fallback method to enable receiving eth when withrdawing from weth contract
*/
receive() external payable {
// required for weth.withdraw() to work properly
require(msg.sender == addresses.weth, "FlashMint: Direct deposits not allowed");
}
}
FlashMintPerp.sol 349 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IQuoter } from "../interfaces/IQuoter.sol";
import { IUniswapV3SwapCallback } from "../interfaces/IUniswapV3SwapCallback.sol";
import { ISwapRouter02 } from "../interfaces/external/ISwapRouter02.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { Context } from "@openzeppelin/contracts/GSN/Context.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TransferHelper } from "../lib/TransferHelper.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
import { ISlippageIssuanceModule } from "../interfaces/ISlippageIssuanceModule.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { Withdrawable } from "external/contracts/aaveV2/utils/Withdrawable.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
/**
* @title FlashMintPerp
*
* Flash issue basis trading products using SlippageIssuanceModule
*
*/
contract FlashMintPerp is Withdrawable {
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using SafeCast for int256;
////////////// State //////////////
ISlippageIssuanceModule public immutable slippageIssuanceModule;
ISwapRouter02 public immutable uniV3Router;
IQuoter public immutable uniV3Quoter;
IERC20 public immutable usdc;
mapping (ISetToken => SetPoolInfo) public setPoolInfo;
mapping(ISetToken => bool) public initializedSets;
////////////// Structs //////////////
struct SetPoolInfo {
bytes spotToUsdcRoute;
address spotToken;
}
////////////// Constructor //////////////
constructor(
ISwapRouter02 _uniV3Router,
IQuoter _uniV3Quoter,
ISlippageIssuanceModule _slippageIssuanceModule,
IERC20 _usdc
)
public
{
uniV3Router = _uniV3Router;
uniV3Quoter = _uniV3Quoter;
slippageIssuanceModule = _slippageIssuanceModule;
usdc = _usdc;
// Approve USDC to SlippageIssuanceModule
_usdc.approve(address(_slippageIssuanceModule), PreciseUnitMath.maxUint256());
// Approve USDC
_usdc.approve(address(_uniV3Router), PreciseUnitMath.maxUint256());
}
///////////// Modifier ///////////////
modifier isInitializedSet(ISetToken _setToken) {
require(initializedSets[_setToken], "Set not initialized");
_;
}
////////// Helper functions /////////////
/**
* @dev Approve specific amount of token to spender
*
* @param _token Address of the token which needs approval
* @param _spender Address of the spender which will be approved to spend token. (Must be a whitlisted issuance module)
* @param _amount The amount of tokens to approve
*/
function approve(address _token, address _spender, uint256 _amount) external onlyOwner {
TransferHelper.safeApprove(_token, _spender, _amount);
}
/**
* @dev Enable the SetToken issuance
*
* @param _setToken Address of the SetToken to be issued
* @param _spotToUsdcRoute Uniswap V3 Path to be used for exchange
* @param _spotToken Address of the spot token
*/
function initializeSet(
ISetToken _setToken,
bytes calldata _spotToUsdcRoute,
address _spotToken
)
external
onlyOwner
{
// Approve spot token to V3 and SIM
TransferHelper.safeApprove(_spotToken, address(uniV3Router), PreciseUnitMath.maxUint256());
TransferHelper.safeApprove(_spotToken, address(slippageIssuanceModule), PreciseUnitMath.maxUint256());
// Store SetToken pool data in mapping
setPoolInfo[_setToken] = SetPoolInfo({
spotToUsdcRoute: _spotToUsdcRoute,
spotToken: _spotToken
});
initializedSets[_setToken] = true;
}
/**
* @dev Disable the SetToken issuance
*
* @param _setToken Address of the SetToken to be issued
*/
function removeSet(ISetToken _setToken) external onlyOwner {
delete setPoolInfo[_setToken];
initializedSets[_setToken] = false;
}
///////////////// Getter Functions /////////////////////
/**
* Returns USDC amount required for issuance
*
* @param _setToken Address of the SetToken
* @param _amountOut The issuance amount of the SetToken
*/
function getUsdcAmountInForFixedSetOffChain(
ISetToken _setToken,
uint256 _amountOut
)
external
returns (uint256 totalUsdcAmountIn)
{
// Get units and components
(
address[] memory slippageIssuanceComponents,
uint256[] memory slippageIssuanceUnits,
) = slippageIssuanceModule.getRequiredComponentIssuanceUnitsOffChain(
_setToken,
_amountOut
);
// Assert assumptions
require(slippageIssuanceComponents.length <= 2, "invalid set");
// calculate total usdc amount in and usdcForSpot
for (uint256 i = 0; i < slippageIssuanceComponents.length; i++) {
if (slippageIssuanceComponents[i] == address(usdc)) {
totalUsdcAmountIn = totalUsdcAmountIn.add(slippageIssuanceUnits[i]);
} else {
uint256 usdcForSpot = uniV3Quoter.quoteExactOutput(
setPoolInfo[_setToken].spotToUsdcRoute,
slippageIssuanceUnits[i].add(1) // Add 1 wei
);
totalUsdcAmountIn = totalUsdcAmountIn.add(usdcForSpot);
}
}
}
/**
* Returns USDC amount required for redemption
*
* @param _setToken Address of the SetToken
* @param _amountIn The redeem amount of the SetToken
*/
function getUsdcAmountOutForFixedSetOffChain(
ISetToken _setToken,
uint256 _amountIn
)
external
returns (uint256 totalUsdcAmountOut)
{
// Get underlying spot and usdc units
(
address[] memory slippageIssuanceComponents,
uint256[] memory slippageIssuanceUnits,
) = slippageIssuanceModule.getRequiredComponentRedemptionUnitsOffChain(
_setToken,
_amountIn
);
// Assert assumptions
require(slippageIssuanceComponents.length <= 2, "invalid set");
// calculate total usdc amount in and usdcFromSpot
for (uint256 i = 0; i < slippageIssuanceComponents.length; i++) {
if (slippageIssuanceComponents[i] == address(usdc)) {
totalUsdcAmountOut = totalUsdcAmountOut.add(slippageIssuanceUnits[i]);
} else {
uint256 usdcFromSpot = uniV3Quoter.quoteExactInput(
setPoolInfo[_setToken].spotToUsdcRoute,
slippageIssuanceUnits[i].sub(1) // Leave 1 wei
);
totalUsdcAmountOut = totalUsdcAmountOut.add(usdcFromSpot);
}
}
}
//////////////// External Functions ////////////////////
/**
* Issue expected amount of SetToken using USDC
*
* @param _setToken Address of the SetToken
* @param _amount The expected issuance amount of the SetToken
* @param _maxAmountIn The maximum input amount of USDC
*/
function issueFixedSetFromUsdc(
ISetToken _setToken,
uint256 _amount,
uint256 _maxAmountIn
)
external
isInitializedSet(_setToken)
{
// Transfer max amount in
TransferHelper.safeTransferFrom(address(usdc), msg.sender, address(this), _maxAmountIn);
// calculate spot asset quantity
uint256 spotAssetQuantity = _spotAssetQuantity(_setToken, _amount);
// Trade USDC for exact spot token
ISwapRouter02.ExactOutputParams memory spotTokenParams = ISwapRouter02.ExactOutputParams(
setPoolInfo[_setToken].spotToUsdcRoute,
address(this),
spotAssetQuantity.add(1), // Add 1 wei
PreciseUnitMath.maxUint256() // No need for slippage check
);
// Executes the swap
uniV3Router.exactOutput(spotTokenParams);
// Issue Set with spot tokens and USDC
slippageIssuanceModule.issueWithSlippage(
_setToken,
_amount,
new address[](0), // No need to check for slippage cause L2; If not enough USDC then issue would fail
new uint256[](0),
msg.sender
);
// Return unused USDC
uint256 usdcBalance = usdc.balanceOf(address(this));
TransferHelper.safeTransfer(address(usdc), msg.sender, usdcBalance);
}
/**
* Redeem expected amount of SetToken using USDC
*
* @param _setToken Address of the SetToken
* @param _amount The expected redeem amount of the SetToken
* @param _minAmountOut The minimum output amount of USDC
*/
function redeemFixedSetForUsdc(
ISetToken _setToken,
uint256 _amount,
uint256 _minAmountOut
)
external
isInitializedSet(_setToken)
{
TransferHelper.safeTransferFrom(address(_setToken), msg.sender, address(this), _amount);
// Redeem Set to spot tokens and USDC
slippageIssuanceModule.redeemWithSlippage(
_setToken,
_amount,
new address[](0), // No need to check for slippage as there is no risk of sandwiching due to flashloans
new uint256[](0),
address(this)
);
// calculate spot asset quantity
uint256 spotAssetQuantity = _spotAssetQuantity(_setToken, _amount);
// check with actual spot token balance
uint256 spotTokenBalance = IERC20(setPoolInfo[_setToken].spotToken).balanceOf(address(this));
if (spotAssetQuantity > spotTokenBalance) {
spotAssetQuantity = spotTokenBalance;
}
ISwapRouter02.ExactInputParams memory spotTokenParams = ISwapRouter02.ExactInputParams(
setPoolInfo[_setToken].spotToUsdcRoute,
address(this),
spotAssetQuantity.sub(1), // Leave 1 wei
0 // No need for slippage check
);
// Executes the swap
uniV3Router.exactInput(spotTokenParams);
// Return the USDC
uint256 usdcBalance = usdc.balanceOf(address(this));
require(usdcBalance >= _minAmountOut, "Not enough USDC");
TransferHelper.safeTransfer(address(usdc), msg.sender, usdcBalance);
}
/////////////// Internal functions //////////////////
function _spotAssetQuantity(ISetToken _setToken, uint256 _amount) internal view returns (uint256) {
address spotAsset = setPoolInfo[_setToken].spotToken;
uint256 spotAssetQuantity = _setToken
.getDefaultPositionRealUnit(spotAsset)
.toUint256()
.preciseMul(_amount);
return spotAssetQuantity;
}
}
FlashMintWrapped.sol 1025 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address} from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
import { ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IController } from "../interfaces/IController.sol";
import { IIntegrationRegistry } from "../interfaces/IIntegrationRegistry.sol";
import { IWrapV2Adapter} from "../interfaces/IWrapV2Adapter.sol";
import { IDebtIssuanceModule} from "../interfaces/IDebtIssuanceModule.sol";
import { ISetToken} from "../interfaces/ISetToken.sol";
import { IWETH} from "../interfaces/IWETH.sol";
import { IWrapModuleV2} from "../interfaces/IWrapModuleV2.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { DEXAdapterV3 } from "./DEXAdapterV3.sol";
/**
* @title FlashMintWrapped
*
* Flash issues SetTokens whose components contain wrapped tokens.
*
* Compatible with:
* IssuanceModules: DebtIssuanceModule, DebtIssuanceModuleV2
* WrapAdapters: IWrapV2Adapter
*
* Supports flash minting for sets that contain both unwrapped and wrapped components.
* Does not support debt positions on Set token.
* Wrapping / Unwrapping is skipped for a component if ComponentSwapData[component_index].underlyingERC20 address == set component address
*
* If the set contains both the wrapped and unwrapped version of a token (e.g. DAI and cDAI)
* -> two separate component data points have to be supplied in _swapData and _wrapData
* e.g. for issue
* Set components at index 0 = DAI; then -> ComponentSwapData[0].underlyingERC20 = DAI; (no wrapping will happen)
* Set components at index 1 = cDAI; then -> ComponentSwapData[1].underlyingERC20 = DAI; (wrapping will happen)
*/
contract FlashMintWrapped is Ownable, ReentrancyGuard {
using DEXAdapterV3 for DEXAdapterV3.Addresses;
using Address for address payable;
using Address for address;
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for ISetToken;
/* ============ Structs ============ */
struct ComponentSwapData {
// unwrapped token version, e.g. DAI
address underlyingERC20;
// swap data for DEX operation: fees, path, etc. see DEXAdapterV3.SwapData
DEXAdapterV3.SwapData dexData;
// ONLY relevant for issue, not used for redeem:
// amount that has to be bought of the unwrapped token version (to cover required wrapped component amounts for issuance)
// this amount has to be computed beforehand through the exchange rate of wrapped Component <> unwrappedComponent
// i.e. getRequiredComponentIssuanceUnits() on the IssuanceModule and then convert units through exchange rate to unwrapped component units
// e.g. 300 cDAI needed for issuance of 1 Set token. exchange rate 1cDAI = 0.05 DAI. -> buyUnderlyingAmount = 0.05 DAI * 300 = 15 DAI
uint256 buyUnderlyingAmount;
}
struct ComponentWrapData {
string integrationName; // wrap adapter integration name as listed in the IntegrationRegistry for the wrapModule
bytes wrapData; // optional wrapData passed to the wrapAdapter
}
/* ============ Constants ============= */
uint256 private constant MAX_UINT256 = type(uint256).max;
/* ============ Immutables ============ */
IController public immutable setController;
IDebtIssuanceModule public immutable issuanceModule; // interface is compatible with DebtIssuanceModuleV2
address public immutable wrapModule; // used to obtain a valid wrap adapter
/* ============ State Variables ============ */
DEXAdapterV3.Addresses public dexAdapter;
/* ============ Events ============ */
event FlashMint(
address indexed _recipient, // The recipient address of the minted Set token
ISetToken indexed _setToken, // The minted Set token
IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to mint the Set tokens
uint256 _amountInputToken, // The amount of input tokens used for minting
uint256 _amountSetIssued // The amount of Set tokens received by the recipient
);
event FlashRedeem(
address indexed _recipient, // The recipient address which redeemed the Set token
ISetToken indexed _setToken, // The redeemed Set token
IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
uint256 _amountSetRedeemed, // The amount of Set token redeemed for output tokens
uint256 _amountOutputToken // The amount of output tokens received by the recipient
);
/* ============ Modifiers ============ */
/**
* checks that _setToken is a valid listed set token on the setController
*
* @param _setToken set token to check
*/
modifier isSetToken(ISetToken _setToken) {
require(setController.isSet(address(_setToken)), "FlashMint: INVALID_SET");
_;
}
/**
* checks that _inputToken is the first adress in _path and _outputToken is the last address in _path
*
* @param _path Array of addresses for a DEX swap path
* @param _inputToken input token of DEX swap
* @param _outputToken output token of DEX swap
*/
modifier isValidPath(
address[] memory _path,
address _inputToken,
address _outputToken
) {
if (_inputToken != _outputToken) {
require(
_path[0] == _inputToken ||
(_inputToken == dexAdapter.weth && _path[0] == DEXAdapterV3.ETH_ADDRESS),
"FlashMint: INPUT_TOKEN_NOT_IN_PATH"
);
require(
_path[_path.length - 1] == _outputToken ||
(_outputToken == dexAdapter.weth && _path[_path.length - 1] == DEXAdapterV3.ETH_ADDRESS),
"FlashMint: OUTPUT_TOKEN_NOT_IN_PATH"
);
}
_;
}
/* ========== Constructor ========== */
/**
* Constructor initializes various addresses
*
* @param _dexAddresses Address of quickRouter, sushiRouter, uniV3Router, uniV3Router, curveAddressProvider, curveCalculator and weth.
* @param _setController Set token controller used to verify a given token is a set
* @param _issuanceModule IDebtIssuanceModule used to issue and redeem tokens
* @param _wrapModule WrapModuleV2 used to obtain a valid wrap adapter
*/
constructor(
DEXAdapterV3.Addresses memory _dexAddresses,
IController _setController,
IDebtIssuanceModule _issuanceModule,
address _wrapModule
) public {
dexAdapter = _dexAddresses;
setController = _setController;
issuanceModule = _issuanceModule;
wrapModule = _wrapModule;
}
/* ============ External Functions ============ */
receive() external payable {
// required for weth.withdraw() to work properly
require(msg.sender == dexAdapter.weth, "FlashMint: DEPOSITS_NOT_ALLOWED");
}
/**
* Withdraw slippage to selected address
*
* @param _tokens Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
* @param _to Address to send the tokens to
*/
function withdrawTokens(IERC20[] calldata _tokens, address payable _to) external onlyOwner payable {
for(uint256 i = 0; i < _tokens.length; i++) {
if(address(_tokens[i]) == DEXAdapterV3.ETH_ADDRESS){
_to.sendValue(address(this).balance);
}
else{
_tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
}
}
}
/**
* Runs all the necessary approval functions required before issuing
* or redeeming a SetToken. This function need to be called only once before the first time
* this smart contract is used on any particular SetToken.
*
* @param _setToken Address of the SetToken being initialized
*/
function approveSetToken(ISetToken _setToken) external isSetToken(_setToken) {
address[] memory _components = _setToken.getComponents();
for (uint256 i = 0; i < _components.length; ++i) {
DEXAdapterV3._safeApprove(IERC20(_components[i]), address(issuanceModule), MAX_UINT256);
}
}
/**
* Issues an exact amount of SetTokens for given amount of input ERC20 tokens.
* The excess amount of input tokens is returned.
* The sender must have approved the _maxAmountInputToken for input token to this contract.
*
* @param _setToken Address of the SetToken to be issued
* @param _inputToken Address of the ERC20 input token
* @param _amountSetToken Amount of SetTokens to issue
* @param _maxAmountInputToken Maximum amount of input tokens to be used
* @param _swapData ComponentSwapData (inputToken -> component) for each set component in the same order
* @param _wrapData ComponentWrapData (underlyingERC20 -> wrapped component) for each required set token component in the exact same order
*
* @return totalInputTokenSold Amount of input tokens spent for issuance
*/
function issueExactSetFromERC20(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken,
uint256 _maxAmountInputToken,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _wrapData
) external nonReentrant returns (uint256) {
return
_issueExactSet(
_setToken,
_inputToken,
_amountSetToken,
_maxAmountInputToken,
_swapData,
_wrapData,
false
);
}
/**
* Issues an exact amount of SetTokens for given amount of ETH. Max amount of ETH used is the transferred amount in msg.value.
* The excess amount of input ETH is returned.
*
* @param _setToken Address of the SetToken to be issued
* @param _amountSetToken Amount of SetTokens to issue
* @param _swapData ComponentSwapData (WETH -> component) for each set component in the same order
* @param _wrapData ComponentWrapData (underlyingERC20 -> wrapped component) for each required set token component in the exact same order
*
* @return totalETHSold Amount of ETH spent for issuance
*/
function issueExactSetFromETH(
ISetToken _setToken,
uint256 _amountSetToken,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _wrapData
) external payable nonReentrant returns (uint256) {
// input token for all operations is WETH (any sent in ETH will be wrapped)
IERC20 inputToken = IERC20(dexAdapter.weth);
uint256 maxAmountInputToken = msg.value; // = deposited amount ETH -> WETH
return
_issueExactSet(
_setToken,
inputToken,
_amountSetToken,
maxAmountInputToken,
_swapData,
_wrapData,
true
);
}
/**
* Redeems an exact amount of SetTokens for an ERC20 token.
* The sender must have approved the _amountSetToken of _setToken to this contract.
*
* @param _setToken Address of the SetToken to be redeemed
* @param _outputToken Address of the ERC20 output token
* @param _amountSetToken Amount of SetTokens to redeem
* @param _minOutputReceive Minimum amount of output tokens to be received
* @param _swapData ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
* @param _unwrapData ComponentWrapData (wrapped Set component -> underlyingERC20) for each required set token component in the exact same order
*
* @return outputAmount Amount of output tokens sent to the caller
*/
function redeemExactSetForERC20(
ISetToken _setToken,
IERC20 _outputToken,
uint256 _amountSetToken,
uint256 _minOutputReceive,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _unwrapData
) external nonReentrant returns (uint256) {
return
_redeemExactSet(
_setToken,
_outputToken,
_amountSetToken,
_minOutputReceive,
_swapData,
_unwrapData,
false
);
}
/**
* Redeems an exact amount of SetTokens for ETH.
* The sender must have approved the _amountSetToken of _setToken to this contract.
*
* @param _setToken Address of the SetToken to be redemeed
* @param _amountSetToken Amount of SetTokens to redeem
* @param _minOutputReceive Minimum amount of output tokens to be received
* @param _swapData ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
* @param _unwrapData ComponentWrapData (wrapped Set component -> underlyingERC20) for each required set token component in the exact same order
*
* @return outputAmount Amount of ETH sent to the caller
*/
function redeemExactSetForETH(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _minOutputReceive,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _unwrapData
) external nonReentrant returns (uint256) {
// output token for all operations is WETH (it will be unwrapped in the end and sent as ETH)
IERC20 outputToken = IERC20(dexAdapter.weth);
return
_redeemExactSet(
_setToken,
outputToken,
_amountSetToken,
_minOutputReceive,
_swapData,
_unwrapData,
true
);
}
/**
* ESTIMATES the amount of output ERC20 tokens required to issue an exact amount of SetTokens based on component swap data.
* Simulates swapping input to all components in swap data.
* This function is not marked view, but should be static called off-chain.
*
* @param _setToken Address of the SetToken being issued
* @param _inputToken Address of the input token used to pay for issuance
* @param _setAmount Amount of SetTokens to issue
* @param _swapData ComponentSwapData (inputToken -> component) for each set component in the same order
*
* @return amountInputNeeded Amount of tokens needed to issue specified amount of SetTokens
*/
function getIssueExactSet(
ISetToken _setToken,
address _inputToken,
uint256 _setAmount,
ComponentSwapData[] calldata _swapData
)
external
isSetToken(_setToken)
returns(uint256 amountInputNeeded)
{
require(_setAmount > 0, "FlashMint: INVALID_INPUTS");
require(_inputToken != address(0), "FlashMint: INVALID_INPUTS");
for (uint256 i = 0; i < _swapData.length; ++i) {
// if the input token is the swap target token, no swapping is needed
if (_inputToken == _swapData[i].underlyingERC20) {
amountInputNeeded = amountInputNeeded.add(_swapData[i].buyUnderlyingAmount);
continue;
}
// add required input amount to swap to desired buyUnderlyingAmount
uint256 amountInNeeded = dexAdapter.getAmountIn(_swapData[i].dexData, _swapData[i].buyUnderlyingAmount);
amountInputNeeded = amountInputNeeded.add(amountInNeeded);
}
}
/**
* ESTIMATES the amount of output ERC20 tokens received when redeeming an exact amount of SetTokens based on component swap data.
* Simulates swapping all components to the output token in swap data.
* This function is not marked view, but should be static called off-chain.
*
* Note that _swapData.buyUnderlyingAmount has to be specified here with the expected underlying amount received after unwrapping
*
* @param _setToken Address of the SetToken being redeemed
* @param _outputToken Address of the output token expected to teceive (if redeeming to ETH, outputToken here is WETH)
* @param _setAmount Amount of SetTokens to redeem
* @param _swapData ComponentSwapData (component -> outputToken) for each set component in the same order
*
* @return amountOutputReceived Amount of output tokens received
*/
function getRedeemExactSet(
ISetToken _setToken,
address _outputToken,
uint256 _setAmount,
ComponentSwapData[] calldata _swapData
)
external
isSetToken(_setToken)
returns(uint256 amountOutputReceived)
{
require(_setAmount > 0, "FlashMint: INVALID_INPUTS");
require(_outputToken != address(0), "FlashMint: INVALID_INPUTS");
for (uint256 i = 0; i < _swapData.length; ++i) {
// if the output token is the swap target token, no swapping is needed
if (_outputToken == _swapData[i].underlyingERC20) {
amountOutputReceived = amountOutputReceived.add(_swapData[i].buyUnderlyingAmount);
continue;
}
// add received output amount from swap
uint256 swapAmountOut = dexAdapter.getAmountOut(_swapData[i].dexData, _swapData[i].buyUnderlyingAmount);
amountOutputReceived = amountOutputReceived.add(swapAmountOut);
}
}
/* ============ Internal Functions ============ */
/**
* Issues an exact amount of SetTokens for given amount of input. Excess amounts are returned
*
* @param _setToken Address of the SetToken to be issued
* @param _inputToken Address of the ERC20 input token
* @param _amountSetToken Amount of SetTokens to issue
* @param _maxAmountInputToken Maximum amount of input tokens to be used
* @param _swapData ComponentSwapData (input token -> underlyingERC20) for each _requiredComponents in the same order
* @param _wrapData ComponentWrapData (underlyingERC20 -> wrapped component) for each required set token component in the exact same order
* @param _issueFromETH boolean flag to identify if issuing from ETH or from ERC20 tokens
*
* @return totalInputSold Amount of input token spent for issuance
*/
function _issueExactSet(
ISetToken _setToken,
IERC20 _inputToken,
uint256 _amountSetToken,
uint256 _maxAmountInputToken,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _wrapData,
bool _issueFromETH
) internal returns (uint256) {
// 1. validate input params, get required components with amounts and snapshot input token balance before
require(address(_inputToken) != address(0), "FlashMint: INVALID_INPUTS");
uint256 inputTokenBalanceBefore = IERC20(_inputToken).balanceOf(address(this));
// Prevent stack too deep
{
(
address[] memory requiredComponents,
uint256[] memory requiredAmounts
) = _validateIssueParams(
_setToken,
_amountSetToken,
_maxAmountInputToken,
_swapData,
_wrapData
);
// 2. transfer input to this contract
if (_issueFromETH) {
// wrap sent in ETH to WETH for all operations
IWETH(dexAdapter.weth).deposit{value: msg.value}();
} else {
_inputToken.safeTransferFrom(msg.sender, address(this), _maxAmountInputToken);
}
// 3. swap input token to all components, then wrap them if needed
_swapAndWrapComponents(
_inputToken,
_maxAmountInputToken,
_swapData,
_wrapData,
requiredComponents,
requiredAmounts
);
}
// 4. issue set tokens
issuanceModule.issue(_setToken, _amountSetToken, msg.sender);
// 5. ensure not too much of input token was spent (covers case where initial input token balance was > 0)
uint256 spentInputTokenAmount = _validateMaxAmountInputToken(
_inputToken,
inputTokenBalanceBefore,
_maxAmountInputToken
);
// 6. return excess inputs
_returnExcessInput(_inputToken, _maxAmountInputToken, spentInputTokenAmount, _issueFromETH);
// 7. emit event and return amount spent
emit FlashMint(
msg.sender,
_setToken,
_issueFromETH ? IERC20(DEXAdapterV3.ETH_ADDRESS) : _inputToken,
spentInputTokenAmount,
_amountSetToken
);
return spentInputTokenAmount;
}
/**
* Redeems an exact amount of SetTokens.
*
* @param _setToken Address of the SetToken to be issued
* @param _outputToken Address of the ERC20 output token
* @param _amountSetToken Amount of SetTokens to redeem
* @param _minOutputReceive Minimum amount of output tokens to be received
* @param _swapData ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
* @param _unwrapData ComponentWrapData (wrapped Set component -> underlyingERC20) for each _redeemComponents in the same order
* @param _redeemToETH boolean flag to identify if redeeming to ETH or to ERC20 tokens
*
* @return outputAmount Amount of output received
*/
function _redeemExactSet(
ISetToken _setToken,
IERC20 _outputToken,
uint256 _amountSetToken,
uint256 _minOutputReceive,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _unwrapData,
bool _redeemToETH
) internal returns (uint256) {
// 1. validate input params and get required components
(address[] memory redeemComponents, uint256[] memory redeemAmounts) = _validateRedeemParams(
_setToken,
_outputToken,
_amountSetToken,
_swapData,
_unwrapData
);
// 2. transfer set tokens to be redeemed to this
_setToken.safeTransferFrom(msg.sender, address(this), _amountSetToken);
// 3. redeem set tokens
issuanceModule.redeem(_setToken, _amountSetToken, address(this));
// 4. unwrap all components if needed and swap them to output token
uint256 totalOutputTokenObtained = _unwrapAndSwapComponents(
_outputToken,
_swapData,
_unwrapData,
redeemComponents,
redeemAmounts
);
// 5. ensure expected minimum output amount has been obtained
require(totalOutputTokenObtained >= _minOutputReceive, "FlashMint: INSUFFICIENT_OUTPUT_AMOUNT");
// 6. transfer obtained output tokens to msg.sender
_sendObtainedOutputToSender(_outputToken, totalOutputTokenObtained, _redeemToETH);
// 7. emit event and return amount obtained
emit FlashRedeem(
msg.sender,
_setToken,
_redeemToETH ? IERC20(DEXAdapterV3.ETH_ADDRESS) : _outputToken,
_amountSetToken,
totalOutputTokenObtained
);
return totalOutputTokenObtained;
}
/**
* Validates input params for _issueExactSet operations
*
* @param _setToken Address of the SetToken to be redeemed
* @param _amountSetToken Amount of SetTokens to issue
* @param _maxAmountToken Maximum amount of input token to spend
* @param _swapData ComponentSwapData (input token -> underlyingERC20) for each _requiredComponents in the same order
* @param _wrapData ComponentWrapData (underlyingERC20 -> wrapped Set component) for each _requiredComponents in the same order
*
* @return requiredComponents Array of required issuance components gotten from IDebtIssuanceModule.getRequiredComponentIssuanceUnits()
* @return requiredAmounts Array of required issuance component amounts gotten from IDebtIssuanceModule.getRequiredComponentIssuanceUnits()
*/
function _validateIssueParams(
ISetToken _setToken,
uint256 _amountSetToken,
uint256 _maxAmountToken,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _wrapData
)
internal
view
isSetToken(_setToken)
returns (address[] memory requiredComponents, uint256[] memory requiredAmounts)
{
require(_amountSetToken > 0 && _maxAmountToken > 0, "FlashMint: INVALID_INPUTS");
(requiredComponents, requiredAmounts, ) = issuanceModule.getRequiredComponentIssuanceUnits(
_setToken,
_amountSetToken
);
require(
_wrapData.length == _swapData.length && _wrapData.length == requiredComponents.length,
"FlashMint: MISMATCH_INPUT_ARRAYS"
);
}
/**
* Validates input params for _redeemExactSet operations
*
* @param _setToken Address of the SetToken to be redeemed
* @param _outputToken Output token that will be redeemed to
* @param _amountSetToken Amount of SetTokens to redeem
* @param _swapData ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
* @param _unwrapData ComponentWrapData (wrapped Set component -> underlyingERC20) for each _redeemComponents in the same order
*
* @return redeemComponents Array of redemption components gotten from IDebtIssuanceModule.getRequiredComponentRedemptionUnits()
* @return redeemAmounts Array of redemption component amounts gotten from IDebtIssuanceModule.getRequiredComponentRedemptionUnits()
*/
function _validateRedeemParams(
ISetToken _setToken,
IERC20 _outputToken,
uint256 _amountSetToken,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _unwrapData
)
internal
view
isSetToken(_setToken)
returns (address[] memory redeemComponents, uint256[] memory redeemAmounts)
{
require(
_amountSetToken > 0 && address(_outputToken) != address(0),
"FlashMint: INVALID_INPUTS"
);
(redeemComponents, redeemAmounts, ) = issuanceModule.getRequiredComponentRedemptionUnits(
_setToken,
_amountSetToken
);
require(
_unwrapData.length == _swapData.length && _unwrapData.length == redeemComponents.length,
"FlashMint: MISMATCH_INPUT_ARRAYS"
);
}
/**
* Swaps and then wraps each _requiredComponents sequentially based on _swapData and _wrapData
*
* @param _inputToken Input token that will be sold
* @param _maxAmountInputToken Maximum amount of input token that can be spent
* @param _swapData ComponentSwapData (input token -> underlyingERC20) for each _requiredComponents in the same order
* @param _wrapData ComponentWrapData (underlyingERC20 -> wrapped Set component) for each _requiredComponents in the same order
* @param _requiredComponents Issuance components gotten from IDebtIssuanceModule.getRequiredComponentIssuanceUnits()
* @param _requiredAmounts Issuance units gotten from IDebtIssuanceModule.getRequiredComponentIssuanceUnits()
*/
function _swapAndWrapComponents(
IERC20 _inputToken,
uint256 _maxAmountInputToken,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _wrapData,
address[] memory _requiredComponents,
uint256[] memory _requiredAmounts
) internal {
// if the required set components contain the input token, we have to make sure that the required amount
// for issuance is actually still left over at the end of swapping and wrapping
uint256 requiredLeftOverInputTokenAmount = 0;
// for each component in the swapData / wrapData / requiredComponents array:
// 1. swap from input token to unwrapped component (exact to buyUnderlyingAmount)
// 2. wrap from unwrapped component to wrapped component (unless unwrapped component == wrapped component)
// 3. ensure amount in contract covers required amount for issuance
for (uint256 i = 0; i < _requiredComponents.length; ++i) {
// if the required set component is the input token, no swapping or wrapping is needed
uint256 requiredAmount = _requiredAmounts[i];
if (address(_inputToken) == _requiredComponents[i]) {
requiredLeftOverInputTokenAmount = requiredAmount;
continue;
}
// snapshot balance of required component before swap and wrap operations
uint256 componentBalanceBefore = IERC20(_requiredComponents[i]).balanceOf(address(this));
// swap input token to underlying token
_swapToExact(
_inputToken, // input
IERC20(_swapData[i].underlyingERC20), // output
_swapData[i].buyUnderlyingAmount, // buy amount: must come from flash mint caller, we do not know the exchange rate wrapped <> unwrapped
_maxAmountInputToken, // maximum spend amount: _maxAmountInputToken as transferred by the flash mint caller
_swapData[i].dexData // dex path fees data etc.
);
// transform underlying token into wrapped version (unless it's the same)
if (_swapData[i].underlyingERC20 != _requiredComponents[i]) {
_wrapComponent(
_wrapData[i],
_swapData[i].buyUnderlyingAmount,
_swapData[i].underlyingERC20,
_requiredComponents[i]
);
}
// ensure obtained component amount covers required component amount for issuance
// this is not already covered through _swapToExact because it does not take wrapping into consideration
uint256 componentBalanceAfter = IERC20(_requiredComponents[i]).balanceOf(address(this));
uint256 componentAmountObtained = componentBalanceAfter.sub(componentBalanceBefore);
require(componentAmountObtained >= requiredAmount, "FlashMint: UNDERBOUGHT_COMPONENT");
}
// ensure left over input token amount covers issuance for component if input token is one of the Set components
require(
IERC20(_inputToken).balanceOf(address(this)) >= requiredLeftOverInputTokenAmount,
"FlashMint: NOT_ENOUGH_INPUT"
);
}
/**
* Unwraps and then swaps each _redeemComponents sequentially based on _swapData and _unwrapData
*
* @param _outputToken Output token that will be bought
* @param _swapData ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
* @param _unwrapData ComponentWrapData (wrapped Set component -> underlyingERC20) for each _redeemComponents in the same order
* @param _redeemComponents redemption components gotten from IDebtIssuanceModule.getRequiredComponentRedemptionUnits()
* @param _redeemAmounts redemption units gotten from IDebtIssuanceModule.getRequiredComponentRedemptionUnits()
*
* @return totalOutputTokenObtained total output token amount obtained
*/
function _unwrapAndSwapComponents(
IERC20 _outputToken,
ComponentSwapData[] calldata _swapData,
ComponentWrapData[] calldata _unwrapData,
address[] memory _redeemComponents,
uint256[] memory _redeemAmounts
) internal returns (uint256 totalOutputTokenObtained) {
// for each component in the swapData / wrapData / redeemComponents array:
// 1. unwrap from wrapped set component to unwrapped underlyingERC20 in swapData
// 2. swap from underlyingERC20 token to output token (exact from obtained underlyingERC20 amount)
for (uint256 i = 0; i < _redeemComponents.length; ++i) {
// default redeemed amount is maximum possible amount that was redeemed for this component
// this is recomputed if the redeemed amount is unwrapped to the actual unwrapped amount
uint256 redeemedAmount = _redeemAmounts[i];
// if the set component is the output token, no swapping or wrapping is needed
if (address(_outputToken) == _redeemComponents[i]) {
// add maximum possible amount that was redeemed for this component to totalOutputTokenObtained (=redeemedAmount)
totalOutputTokenObtained = totalOutputTokenObtained.add(redeemedAmount);
continue;
}
// transform wrapped token into unwrapped version (unless it's the same)
if (_swapData[i].underlyingERC20 != _redeemComponents[i]) {
// snapshot unwrapped balance before to compute the actual redeemed amount of underlying token (due to unknown exchange rate)
uint256 unwrappedBalanceBefore = IERC20(_swapData[i].underlyingERC20).balanceOf(
address(this)
);
_unwrapComponent(
_unwrapData[i],
redeemedAmount,
_swapData[i].underlyingERC20,
_redeemComponents[i]
);
// recompute actual redeemed amount to the underlyingERC20 token amount obtained through unwrapping
uint256 unwrappedBalanceAfter = IERC20(_swapData[i].underlyingERC20).balanceOf(
address(this)
);
redeemedAmount = unwrappedBalanceAfter.sub(unwrappedBalanceBefore);
}
// swap redeemed and unwrapped component to output token
uint256 boughtOutputTokenAmount = _swapFromExact(
IERC20(_swapData[i].underlyingERC20), // input
_outputToken, // output
redeemedAmount, // sell amount of input token
_swapData[i].dexData // dex path fees data etc.
);
totalOutputTokenObtained = totalOutputTokenObtained.add(boughtOutputTokenAmount);
}
}
/**
* Swaps _inputToken to exact _amount to _outputToken through _swapDexData
*
* @param _inputToken Input token that will be sold
* @param _outputToken Output token that will be bought
* @param _amount Amount that will be bought
* @param _maxAmountIn Maximum aount of input token that can be spent
* @param _swapDexData DEXAdapterV3.SwapData with path, fees, etc. for inputToken -> outputToken swap
*
* @return Amount of spent _inputToken
*/
function _swapToExact(
IERC20 _inputToken,
IERC20 _outputToken,
uint256 _amount,
uint256 _maxAmountIn,
DEXAdapterV3.SwapData calldata _swapDexData
)
internal
isValidPath(_swapDexData.path, address(_inputToken), address(_outputToken))
returns (uint256)
{
// safe approves are done right in the dexAdapter library
// swaps are skipped there too if inputToken == outputToken (depending on path)
return dexAdapter.swapTokensForExactTokens(_amount, _maxAmountIn, _swapDexData);
}
/**
* Swaps exact _amount of _inputToken to _outputToken through _swapDexData
*
* @param _inputToken Input token that will be sold
* @param _outputToken Output token that will be bought
* @param _amount Amount that will be sold
* @param _swapDexData DEXAdapterV3.SwapData with path, fees, etc. for inputToken -> outputToken swap
*
* @return amount of received _outputToken
*/
function _swapFromExact(
IERC20 _inputToken,
IERC20 _outputToken,
uint256 _amount,
DEXAdapterV3.SwapData calldata _swapDexData
)
internal
isValidPath(_swapDexData.path, address(_inputToken), address(_outputToken))
returns (uint256)
{
// safe approves are done right in the dexAdapter library
return
dexAdapter.swapExactTokensForTokens(
_amount,
// _minAmountOut is 0 here since we don't know what to check against because for wrapped components
// we only have the required amounts for the wrapped component, but not for the underlying we swap to here
// This is covered indirectly in later checks though
// e.g. directly through the issue call (not enough _outputToken -> wrappedComponent -> issue will fail)
0,
_swapDexData
);
}
/**
* Wraps _wrapAmount of _underlyingToken to _wrappedComponent component
*
* @param _wrapData ComponentWrapData including integration name and optional wrap calldata bytes
* @param _wrapAmount amount of _underlyingToken to wrap
* @param _underlyingToken underlying (unwrapped) token to wrap from (e.g. DAI)
* @param _wrappedToken wrapped token to wrap to
*/
function _wrapComponent(
ComponentWrapData calldata _wrapData,
uint256 _wrapAmount,
address _underlyingToken,
address _wrappedToken
) internal {
// 1. get the wrap adapter directly from the integration registry
// Note we could get the address of the adapter directly in the params instead of the integration name
// but that would allow integrators to use their own potentially somehow malicious WrapAdapter
// by directly fetching it from our IntegrationRegistry we can be sure that it behaves as expected
IWrapV2Adapter _wrapAdapter = IWrapV2Adapter(_getAndValidateAdapter(_wrapData.integrationName));
// 2. get wrap call info from adapter
(address _wrapCallTarget, uint256 _wrapCallValue, bytes memory _wrapCallData) = _wrapAdapter
.getWrapCallData(
_underlyingToken,
_wrappedToken,
_wrapAmount,
address(this),
_wrapData.wrapData
);
// 3. approve token transfer from this to _wrapCallTarget
DEXAdapterV3._safeApprove(
IERC20(_underlyingToken),
_wrapCallTarget,
_wrapAmount
);
// 4. invoke wrap function call. we can't check any response value because the implementation might be different
// between wrapCallTargets... e.g. compoundV2 would return uint256 with value 0 if successful
_wrapCallTarget.functionCallWithValue(_wrapCallData, _wrapCallValue);
}
/**
* Unwraps _unwrapAmount of _wrappedToken to _underlyingToken
*
* @param _unwrapData ComponentWrapData including integration name and optional wrap calldata bytes
* @param _unwrapAmount amount of _underlyingToken to wrap
* @param _underlyingToken underlying (unwrapped) token to unwrap to (e.g. DAI)
* @param _wrappedToken wrapped token to unwrap from
*/
function _unwrapComponent(
ComponentWrapData calldata _unwrapData,
uint256 _unwrapAmount,
address _underlyingToken,
address _wrappedToken
) internal {
// 1. get the wrap adapter directly from the integration registry
// Note we could get the address of the adapter directly in the params instead of the integration name
// but that would allow integrators to use their own potentially somehow malicious WrapAdapter
// by directly fetching it from our IntegrationRegistry we can be sure that it behaves as expected
IWrapV2Adapter _wrapAdapter = IWrapV2Adapter(_getAndValidateAdapter(_unwrapData.integrationName));
// 2. get unwrap call info from adapter
(address _wrapCallTarget, uint256 _wrapCallValue, bytes memory _wrapCallData) = _wrapAdapter
.getUnwrapCallData(
_underlyingToken,
_wrappedToken,
_unwrapAmount,
address(this),
_unwrapData.wrapData
);
// 3. invoke unwrap function call. we can't check any response value because the implementation might be different
// between wrapCallTargets... e.g. compoundV2 would return uint256 with value 0 if successful
_wrapCallTarget.functionCallWithValue(_wrapCallData, _wrapCallValue);
}
/**
* Validates that not more than the requested max amount of the input token has been spent
*
* @param _inputToken Address of the input token to return
* @param _inputTokenBalanceBefore input token balance before at the beginning of the operation
* @param _maxAmountInputToken maximum amount that could be spent
* @return spentInputTokenAmount actual spent amount of the input token
*/
function _validateMaxAmountInputToken(
IERC20 _inputToken,
uint256 _inputTokenBalanceBefore,
uint256 _maxAmountInputToken
) internal view returns (uint256 spentInputTokenAmount) {
uint256 inputTokenBalanceAfter = _inputToken.balanceOf(address(this));
// _maxAmountInputToken amount has been transferred to this contract after _inputTokenBalanceBefore snapshot
spentInputTokenAmount = _inputTokenBalanceBefore.add(_maxAmountInputToken).sub(
inputTokenBalanceAfter
);
require(spentInputTokenAmount <= _maxAmountInputToken, "FlashMint: OVERSPENT_INPUT_TOKEN");
}
/**
* Returns excess input token
*
* @param _inputToken Address of the input token to return
* @param _receivedAmount Amount received by the caller
* @param _spentAmount Amount spent for issuance
* @param _returnETH Boolean flag to identify if ETH should be returned or the input token
*/
function _returnExcessInput(
IERC20 _inputToken,
uint256 _receivedAmount,
uint256 _spentAmount,
bool _returnETH
) internal {
uint256 amountTokenReturn = _receivedAmount.sub(_spentAmount);
if (amountTokenReturn > 0) {
if (_returnETH) {
// unwrap from WETH -> ETH and send ETH amount back to sender
IWETH(dexAdapter.weth).withdraw(amountTokenReturn);
(payable(msg.sender)).sendValue(amountTokenReturn);
} else {
_inputToken.safeTransfer(msg.sender, amountTokenReturn);
}
}
}
/**
* Sends the obtained amount of output token / ETH to msg.sender
*
* @param _outputToken Address of the output token to return
* @param _amount Amount to transfer
* @param _redeemToETH Boolean flag to identify if ETH or the output token should be sent
*/
function _sendObtainedOutputToSender(
IERC20 _outputToken,
uint256 _amount,
bool _redeemToETH
) internal {
if (_redeemToETH) {
// unwrap from WETH -> ETH and send ETH amount back to sender
IWETH(dexAdapter.weth).withdraw(_amount);
(payable(msg.sender)).sendValue(_amount);
} else {
_outputToken.safeTransfer(msg.sender, _amount);
}
}
/**
* Gets the integration for the passed in integration name listed on the wrapModule. Validates that the address is not empty
*
* @param _integrationName Name of wrap adapter integration (mapping on integration registry)
*
* @return adapter address of the wrap adapter
*/
function _getAndValidateAdapter(string memory _integrationName)
internal
view
returns (address adapter)
{
// integration registry has resourceId 0, see library ResourceIdentifier
// @dev could also be accomplished with using ResourceIdentifier for IController but this results in less bloat in the repo
IIntegrationRegistry integrationRegistry = IIntegrationRegistry(setController.resourceId(0));
adapter = integrationRegistry.getIntegrationAdapterWithHash(
wrapModule,
keccak256(bytes(_integrationName))
);
require(adapter != address(0), "FlashMint: WRAP_ADAPTER_INVALID");
}
}
DelegatedManagerFactory.sol 469 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ISetTokenCreator } from "../interfaces/ISetTokenCreator.sol";
import { DelegatedManager } from "../manager/DelegatedManager.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
/**
* @title DelegatedManagerFactory
* @author Set Protocol
*
* Factory smart contract which gives asset managers the ability to:
* > create a Set Token managed with a DelegatedManager contract
* > create a DelegatedManager contract for an existing Set Token to migrate to
* > initialize extensions and modules for SetTokens using the DelegatedManager system
*/
contract DelegatedManagerFactory {
using AddressArrayUtils for address[];
using Address for address;
/* ============ Structs ============ */
struct InitializeParams{
address deployer;
address owner;
address methodologist;
IDelegatedManager manager;
bool isPending;
}
/* ============ Events ============ */
/**
* @dev Emitted on DelegatedManager creation
* @param _setToken Instance of the SetToken being created
* @param _manager Address of the DelegatedManager
* @param _deployer Address of the deployer
*/
event DelegatedManagerCreated(
ISetToken indexed _setToken,
DelegatedManager indexed _manager,
address _deployer
);
/**
* @dev Emitted on DelegatedManager initialization
* @param _setToken Instance of the SetToken being initialized
* @param _manager Address of the DelegatedManager owner
*/
event DelegatedManagerInitialized(
ISetToken indexed _setToken,
IDelegatedManager indexed _manager
);
/* ============ State Variables ============ */
// ManagerCore address
IManagerCore public immutable managerCore;
// Controller address
IController public immutable controller;
// SetTokenFactory address
ISetTokenCreator public immutable setTokenFactory;
// Mapping which stores manager creation metadata between creation and initialization steps
mapping(ISetToken=>InitializeParams) public initializeState;
/* ============ Constructor ============ */
/**
* @dev Sets managerCore and setTokenFactory address.
* @param _managerCore Address of ManagerCore protocol contract
* @param _controller Address of Controller protocol contract
* @param _setTokenFactory Address of SetTokenFactory protocol contract
*/
constructor(
IManagerCore _managerCore,
IController _controller,
ISetTokenCreator _setTokenFactory
)
public
{
managerCore = _managerCore;
controller = _controller;
setTokenFactory = _setTokenFactory;
}
/* ============ External Functions ============ */
/**
* ANYONE CAN CALL: Deploys a new SetToken and DelegatedManager. Sets some temporary metadata about
* the deployment which will be read during a subsequent intialization step which wires everything
* together.
*
* @param _components List of addresses of components for initial Positions
* @param _units List of units. Each unit is the # of components per 10^18 of a SetToken
* @param _name Name of the SetToken
* @param _symbol Symbol of the SetToken
* @param _owner Address to set as the DelegateManager's `owner` role
* @param _methodologist Address to set as the DelegateManager's methodologist role
* @param _modules List of modules to enable. All modules must be approved by the Controller
* @param _operators List of operators authorized for the DelegateManager
* @param _assets List of assets DelegateManager can trade. When empty, asset allow list is not enforced
* @param _extensions List of extensions authorized for the DelegateManager
*
* @return (ISetToken, address) The created SetToken and DelegatedManager addresses, respectively
*/
function createSetAndManager(
address[] memory _components,
int256[] memory _units,
string memory _name,
string memory _symbol,
address _owner,
address _methodologist,
address[] memory _modules,
address[] memory _operators,
address[] memory _assets,
address[] memory _extensions
)
external
returns (ISetToken, address)
{
_validateManagerParameters(_components, _extensions, _assets);
ISetToken setToken = _deploySet(
_components,
_units,
_modules,
_name,
_symbol
);
DelegatedManager manager = _deployManager(
setToken,
_extensions,
_operators,
_assets
);
_setInitializationState(setToken, address(manager), _owner, _methodologist);
return (setToken, address(manager));
}
/**
* ONLY SETTOKEN MANAGER: Deploys a DelegatedManager and sets some temporary metadata about the
* deployment which will be read during a subsequent intialization step which wires everything together.
* This method is used when migrating an existing SetToken to the DelegatedManager system.
*
* (Note: This flow should work well for SetTokens managed by an EOA. However, existing
* contract-managed Sets may need to have their ownership temporarily transferred to an EOA when
* migrating. We don't anticipate high demand for this migration case though.)
*
* @param _setToken Instance of SetToken to migrate to the DelegatedManager system
* @param _owner Address to set as the DelegateManager's `owner` role
* @param _methodologist Address to set as the DelegateManager's methodologist role
* @param _operators List of operators authorized for the DelegateManager
* @param _assets List of assets DelegateManager can trade. When empty, asset allow list is not enforced
* @param _extensions List of extensions authorized for the DelegateManager
*
* @return (address) Address of the created DelegatedManager
*/
function createManager(
ISetToken _setToken,
address _owner,
address _methodologist,
address[] memory _operators,
address[] memory _assets,
address[] memory _extensions
)
external
returns (address)
{
require(controller.isSet(address(_setToken)), "Must be controller-enabled SetToken");
require(msg.sender == _setToken.manager(), "Must be manager");
_validateManagerParameters(_setToken.getComponents(), _extensions, _assets);
DelegatedManager manager = _deployManager(
_setToken,
_extensions,
_operators,
_assets
);
_setInitializationState(_setToken, address(manager), _owner, _methodologist);
return address(manager);
}
/**
* ONLY DEPLOYER: Wires SetToken, DelegatedManager, global manager extensions, and modules together
* into a functioning package.
*
* NOTE: When migrating to this manager system from an existing SetToken, the SetToken's current manager address
* must be reset to point at the newly deployed DelegatedManager contract in a separate, final transaction.
*
* @param _setToken Instance of the SetToken
* @param _ownerFeeSplit Percent of fees in precise units (10^16 = 1%) sent to owner, rest to methodologist
* @param _ownerFeeRecipient Address which receives owner's share of fees when they're distributed
* @param _extensions List of addresses of extensions which need to be initialized
* @param _initializeBytecode List of bytecode encoded calls to relevant target's initialize function
*/
function initialize(
ISetToken _setToken,
uint256 _ownerFeeSplit,
address _ownerFeeRecipient,
address[] memory _extensions,
bytes[] memory _initializeBytecode
)
external
{
require(initializeState[_setToken].isPending, "Manager must be awaiting initialization");
require(msg.sender == initializeState[_setToken].deployer, "Only deployer can initialize manager");
_extensions.validatePairsWithArray(_initializeBytecode);
IDelegatedManager manager = initializeState[_setToken].manager;
// If the SetToken was factory-deployed & factory is its current `manager`, transfer
// managership to the new DelegatedManager
if (_setToken.manager() == address(this)) {
_setToken.setManager(address(manager));
}
_initializeExtensions(manager, _extensions, _initializeBytecode);
_setManagerState(
manager,
initializeState[_setToken].owner,
initializeState[_setToken].methodologist,
_ownerFeeSplit,
_ownerFeeRecipient
);
delete initializeState[_setToken];
emit DelegatedManagerInitialized(_setToken, manager);
}
/* ============ Internal Functions ============ */
/**
* Deploys a SetToken, setting this factory as its manager temporarily, pending initialization.
* Managership is transferred to a newly created DelegatedManager during `initialize`
*
* @param _components List of addresses of components for initial Positions
* @param _units List of units. Each unit is the # of components per 10^18 of a SetToken
* @param _modules List of modules to enable. All modules must be approved by the Controller
* @param _name Name of the SetToken
* @param _symbol Symbol of the SetToken
*
* @return Address of created SetToken;
*/
function _deploySet(
address[] memory _components,
int256[] memory _units,
address[] memory _modules,
string memory _name,
string memory _symbol
)
internal
returns (ISetToken)
{
address setToken = setTokenFactory.create(
_components,
_units,
_modules,
address(this),
_name,
_symbol
);
return ISetToken(setToken);
}
/**
* Deploys a DelegatedManager. Sets owner and methodologist roles to address(this) and the resulting manager address is
* saved to the ManagerCore.
*
* @param _setToken Instance of SetToken to migrate to the DelegatedManager system
* @param _extensions List of extensions authorized for the DelegateManager
* @param _operators List of operators authorized for the DelegateManager
* @param _assets List of assets DelegateManager can trade. When empty, asset allow list is not enforced
*
* @return Address of created DelegatedManager
*/
function _deployManager(
ISetToken _setToken,
address[] memory _extensions,
address[] memory _operators,
address[] memory _assets
)
internal
returns (DelegatedManager)
{
// If asset array is empty, manager's useAssetAllowList will be set to false
// and the asset allow list is not enforced
bool useAssetAllowlist = _assets.length > 0;
DelegatedManager newManager = new DelegatedManager(
_setToken,
address(this),
address(this),
_extensions,
_operators,
_assets,
useAssetAllowlist
);
// Registers manager with ManagerCore
managerCore.addManager(address(newManager));
emit DelegatedManagerCreated(
_setToken,
newManager,
msg.sender
);
return newManager;
}
/**
* Initialize extensions on the DelegatedManager. Checks that extensions are tracked on the ManagerCore and that the
* provided bytecode targets the input manager.
*
* @param _manager Instance of DelegatedManager
* @param _extensions List of addresses of extensions to initialize
* @param _initializeBytecode List of bytecode encoded calls to relevant extensions's initialize function
*/
function _initializeExtensions(
IDelegatedManager _manager,
address[] memory _extensions,
bytes[] memory _initializeBytecode
) internal {
for (uint256 i = 0; i < _extensions.length; i++) {
address extension = _extensions[i];
require(managerCore.isExtension(extension), "Target must be ManagerCore-enabled Extension");
bytes memory initializeBytecode = _initializeBytecode[i];
// Each input initializeBytecode is a varible length bytes array which consists of a 32 byte prefix for the
// length parameter, a 4 byte function selector, a 32 byte DelegatedManager address, and any additional parameters
// as shown below:
// [32 bytes - length parameter, 4 bytes - function selector, 32 bytes - DelegatedManager address, additional parameters]
// It is required that the input DelegatedManager address is the DelegatedManager address corresponding to the caller
address inputManager;
assembly {
inputManager := mload(add(initializeBytecode, 36))
}
require(inputManager == address(_manager), "Must target correct DelegatedManager");
// Because we validate uniqueness of _extensions only one transaction can be sent to each extension during this
// transaction. Due to this no extension can be used for any SetToken transactions other than initializing these contracts
extension.functionCallWithValue(initializeBytecode, 0);
}
}
/**
* Stores temporary creation metadata during the contract creation step. Data is retrieved, read and
* finally deleted during `initialize`.
*
* @param _setToken Instance of SetToken
* @param _manager Address of DelegatedManager created for SetToken
* @param _owner Address that will be given the `owner` DelegatedManager's role on initialization
* @param _methodologist Address that will be given the `methodologist` DelegatedManager's role on initialization
*/
function _setInitializationState(
ISetToken _setToken,
address _manager,
address _owner,
address _methodologist
) internal {
initializeState[_setToken] = InitializeParams({
deployer: msg.sender,
owner: _owner,
methodologist: _methodologist,
manager: IDelegatedManager(_manager),
isPending: true
});
}
/**
* Initialize fee settings on DelegatedManager and transfer `owner` and `methodologist` roles.
*
* @param _manager Instance of DelegatedManager
* @param _owner Address that will be given the `owner` DelegatedManager's role
* @param _methodologist Address that will be given the `methodologist` DelegatedManager's role
* @param _ownerFeeSplit Percent of fees in precise units (10^16 = 1%) sent to owner, rest to methodologist
* @param _ownerFeeRecipient Address which receives owner's share of fees when they're distributed
*/
function _setManagerState(
IDelegatedManager _manager,
address _owner,
address _methodologist,
uint256 _ownerFeeSplit,
address _ownerFeeRecipient
) internal {
_manager.updateOwnerFeeSplit(_ownerFeeSplit);
_manager.updateOwnerFeeRecipient(_ownerFeeRecipient);
_manager.transferOwnership(_owner);
_manager.setMethodologist(_methodologist);
}
/**
* Validates that all components currently held by the Set are on the asset allow list. Validate that the manager is
* deployed with at least one extension in the PENDING state.
*
* @param _components List of addresses of components for initial/current Set positions
* @param _extensions List of extensions authorized for the DelegateManager
* @param _assets List of assets DelegateManager can trade. When empty, asset allow list is not enforced
*/
function _validateManagerParameters(
address[] memory _components,
address[] memory _extensions,
address[] memory _assets
)
internal
pure
{
require(_extensions.length > 0, "Must have at least 1 extension");
if (_assets.length != 0) {
_validateComponentsIncludedInAssetsList(_components, _assets);
}
}
/**
* Validates that all SetToken components are included in the assets whitelist. This prevents the
* DelegatedManager from being initialized with some components in an untrade-able state.
*
* @param _components List of addresses of components for initial Positions
* @param _assets List of assets DelegateManager can trade.
*/
function _validateComponentsIncludedInAssetsList(
address[] memory _components,
address[] memory _assets
) internal pure {
for (uint256 i = 0; i < _components.length; i++) {
require(_assets.contains(_components[i]), "Asset list must include all components");
}
}
}
GlobalAuctionRebalanceExtension.sol 272 lines
/*
Copyright 2023 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
/**
* @title GlobalAuctionRebalanceExtension
* @author Index Coop
*
* @dev Extension contract for interacting with the AuctionRebalanceModuleV1. This contract acts as a pass-through and functions
* are only callable by operator.
*/
contract GlobalAuctionRebalanceExtension is BaseGlobalExtension {
using AddressArrayUtils for address[];
using SafeMath for uint256;
/* ============ Events ============ */
event AuctionRebalanceExtensionInitialized(
address indexed _setToken,
address indexed _delegatedManager
);
/* ============ Structs ============ */
struct AuctionExecutionParams {
uint256 targetUnit; // Target quantity of the component in Set, in precise units (10 ** 18).
string priceAdapterName; // Identifier for the price adapter to be used.
bytes priceAdapterConfigData; // Encoded data for configuring the chosen price adapter.
}
/* ============ State Variables ============ */
IAuctionRebalanceModuleV1 public immutable auctionModule; // AuctionRebalanceModuleV1
/* ============ Constructor ============ */
/**
* @dev Instantiate with ManagerCore address and WrapModuleV2 address.
*
* @param _managerCore Address of ManagerCore contract
* @param _auctionModule Address of AuctionRebalanceModuleV1 contract
*/
constructor(IManagerCore _managerCore, IAuctionRebalanceModuleV1 _auctionModule) public BaseGlobalExtension(_managerCore) {
auctionModule = _auctionModule;
}
/* ============ External Functions ============ */
/**
* @dev ONLY OWNER: Initializes AuctionRebalanceModuleV1 on the SetToken associated with the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize the AuctionRebalanceModuleV1 for
*/
function initializeModule(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
require(_delegatedManager.isInitializedExtension(address(this)), "Extension must be initialized");
_initializeModule(_delegatedManager.setToken(), _delegatedManager);
}
/**
* @dev ONLY OWNER: Initializes AuctionRebalanceExtension to the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
emit AuctionRebalanceExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* @dev ONLY OWNER: Initializes AuctionRebalanceExtension to the DelegatedManager and AuctionRebalanceModuleV1 to the SetToken.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeModuleAndExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager){
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
_initializeModule(setToken, _delegatedManager);
emit AuctionRebalanceExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* @dev ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the AuctionRebalanceExtension
* @dev _removeExtension implements the only manager assertion.
*/
function removeExtension() external override {
IDelegatedManager delegatedManager = IDelegatedManager(msg.sender);
ISetToken setToken = delegatedManager.setToken();
_removeExtension(setToken, delegatedManager);
}
/**
* @dev OPERATOR ONLY: Checks that the old components array matches the current components array and then invokes the
* AuctionRebalanceModuleV1 startRebalance function.
*
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _quoteAsset ERC20 token used as the quote asset in auctions.
* @param _oldComponents Addresses of existing components in the SetToken.
* @param _newComponents Addresses of new components to be added.
* @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents.
* @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to
* the current component positions. Set to 0 for components being removed.
* @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken.
* @param _rebalanceDuration Duration of the rebalance in seconds.
* @param _positionMultiplier Position multiplier at the time target units were calculated.
*/
function startRebalance(
ISetToken _setToken,
IERC20 _quoteAsset,
address[] memory _oldComponents,
address[] memory _newComponents,
AuctionExecutionParams[] memory _newComponentsAuctionParams,
AuctionExecutionParams[] memory _oldComponentsAuctionParams,
bool _shouldLockSetToken,
uint256 _rebalanceDuration,
uint256 _positionMultiplier
)
external
virtual
onlyOperator(_setToken)
{
address[] memory currentComponents = _setToken.getComponents();
require(currentComponents.length == _oldComponents.length, "Mismatch: old and current components length");
for (uint256 i = 0; i < _oldComponents.length; i++) {
require(currentComponents[i] == _oldComponents[i], "Mismatch: old and current components");
}
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.startRebalance.selector,
_setToken,
_quoteAsset,
_newComponents,
_newComponentsAuctionParams,
_oldComponentsAuctionParams,
_shouldLockSetToken,
_rebalanceDuration,
_positionMultiplier
);
_invokeManager(_manager((_setToken)), address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Unlocks SetToken via AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _setToken Address of the SetToken to unlock.
*/
function unlock(ISetToken _setToken) external onlyOperator(_setToken) {
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.unlock.selector,
_setToken
);
_invokeManager(_manager((_setToken)), address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Sets the target raise percentage for all components on AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _setToken Address of the SetToken to update unit targets of.
* @param _raiseTargetPercentage Amount to raise all component's unit targets by (in precise units)
*/
function setRaiseTargetPercentage(ISetToken _setToken, uint256 _raiseTargetPercentage) external onlyOperator(_setToken) {
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.setRaiseTargetPercentage.selector,
_setToken,
_raiseTargetPercentage
);
_invokeManager(_manager((_setToken)), address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Updates the bidding permission status for a list of addresses on AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _setToken Address of the SetToken to rebalance bidder status of.
* @param _bidders An array of addresses whose bidding permission status is to be toggled.
* @param _statuses An array of booleans indicating the new bidding permission status for each corresponding address in `_bidders`.
*/
function setBidderStatus(
ISetToken _setToken,
address[] memory _bidders,
bool[] memory _statuses
)
external
onlyOperator(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.setBidderStatus.selector,
_setToken,
_bidders,
_statuses
);
_invokeManager(_manager((_setToken)), address(auctionModule), callData);
}
/**
* @dev OPERATOR ONLY: Sets whether anyone can bid on the AuctionRebalanceModuleV1.
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _setToken Address of the SetToken to update anyone bid status of.
* @param _status A boolean indicating if anyone can bid.
*/
function setAnyoneBid( ISetToken _setToken, bool _status) external onlyOperator(_setToken) {
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.setAnyoneBid.selector,
_setToken,
_status
);
_invokeManager(_manager((_setToken)), address(auctionModule), callData);
}
/* ============ Internal Functions ============ */
/**
* Internal function to initialize AuctionRebalanceModuleV1 on the SetToken associated with the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize the AuctionRebalanceModuleV1 for
*/
function _initializeModule(ISetToken _setToken, IDelegatedManager _delegatedManager) internal {
bytes memory callData = abi.encodeWithSelector(IAuctionRebalanceModuleV1.initialize.selector, _setToken);
_invokeManager(_delegatedManager, address(auctionModule), callData);
}
}
GlobalBatchTradeExtension.sol 292 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ITradeModule } from "../interfaces/ITradeModule.sol";
import { StringArrayUtils } from "../lib/StringArrayUtils.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
/**
* @title GlobalBatchTradeExtension
* @author Set Protocol
*
* Smart contract global extension which provides DelegatedManager operator(s) the ability to execute a batch of trades
* on a DEX and the owner the ability to restrict operator(s) permissions with an asset whitelist.
*/
contract GlobalBatchTradeExtension is BaseGlobalExtension {
using StringArrayUtils for string[];
/* ============ Structs ============ */
struct TradeInfo {
string exchangeName; // Human readable name of the exchange in the integrations registry
address sendToken; // Address of the token to be sent to the exchange
uint256 sendQuantity; // Max units of `sendToken` sent to the exchange
address receiveToken; // Address of the token that will be received from the exchange
uint256 receiveQuantity; // Min units of `receiveToken` to be received from the exchange
bytes data; // Arbitrary bytes to be used to construct trade call data
}
/* ============ Events ============ */
event IntegrationAdded(
string _integrationName // String name of TradeModule exchange integration to allow
);
event IntegrationRemoved(
string _integrationName // String name of TradeModule exchange integration to disallow
);
event BatchTradeExtensionInitialized(
address indexed _setToken, // Address of the SetToken which had BatchTradeExtension initialized on their manager
address indexed _delegatedManager // Address of the DelegatedManager which initialized the BatchTradeExtension
);
event StringTradeFailed(
address indexed _setToken, // Address of the SetToken which the failed trade targeted
uint256 indexed _index, // Index of trade that failed in _trades parameter of batchTrade call
string _reason, // String reason for the trade failure
TradeInfo _tradeInfo // Input TradeInfo of the failed trade
);
event BytesTradeFailed(
address indexed _setToken, // Address of the SetToken which the failed trade targeted
uint256 indexed _index, // Index of trade that failed in _trades parameter of batchTrade call
bytes _lowLevelData, // Bytes low level data reason for the trade failure
TradeInfo _tradeInfo // Input TradeInfo of the failed trade
);
/* ============ State Variables ============ */
// Instance of TradeModule
ITradeModule public immutable tradeModule;
// List of allowed TradeModule exchange integrations
string[] public integrations;
// Mapping to check whether string is allowed TradeModule exchange integration
mapping(string => bool) public isIntegration;
/* ============ Modifiers ============ */
/**
* Throws if the sender is not the ManagerCore contract owner
*/
modifier onlyManagerCoreOwner() {
require(msg.sender == managerCore.owner(), "Caller must be ManagerCore owner");
_;
}
/* ============ Constructor ============ */
/**
* Instantiate with ManagerCore address, TradeModule address, and allowed TradeModule integration strings.
*
* @param _managerCore Address of ManagerCore contract
* @param _tradeModule Address of TradeModule contract
* @param _integrations List of TradeModule exchange integrations to allow
*/
constructor(
IManagerCore _managerCore,
ITradeModule _tradeModule,
string[] memory _integrations
)
public
BaseGlobalExtension(_managerCore)
{
tradeModule = _tradeModule;
integrations = _integrations;
uint256 integrationsLength = _integrations.length;
for (uint256 i = 0; i < integrationsLength; i++) {
_addIntegration(_integrations[i]);
}
}
/* ============ External Functions ============ */
/**
* MANAGER OWNER ONLY. Allows manager owner to add allowed TradeModule exchange integrations
*
* @param _integrations List of TradeModule exchange integrations to allow
*/
function addIntegrations(string[] memory _integrations) external onlyManagerCoreOwner {
uint256 integrationsLength = _integrations.length;
for (uint256 i = 0; i < integrationsLength; i++) {
require(!isIntegration[_integrations[i]], "Integration already exists");
integrations.push(_integrations[i]);
_addIntegration(_integrations[i]);
}
}
/**
* MANAGER OWNER ONLY. Allows manager owner to remove allowed TradeModule exchange integrations
*
* @param _integrations List of TradeModule exchange integrations to disallow
*/
function removeIntegrations(string[] memory _integrations) external onlyManagerCoreOwner {
uint256 integrationsLength = _integrations.length;
for (uint256 i = 0; i < integrationsLength; i++) {
require(isIntegration[_integrations[i]], "Integration does not exist");
integrations.removeStorage(_integrations[i]);
isIntegration[_integrations[i]] = false;
IntegrationRemoved(_integrations[i]);
}
}
/**
* ONLY OWNER: Initializes TradeModule on the SetToken associated with the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
*/
function initializeModule(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
_initializeModule(_delegatedManager.setToken(), _delegatedManager);
}
/**
* ONLY OWNER: Initializes BatchTradeExtension to the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
emit BatchTradeExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY OWNER: Initializes BatchTradeExtension to the DelegatedManager and TradeModule to the SetToken
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeModuleAndExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager){
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
_initializeModule(setToken, _delegatedManager);
emit BatchTradeExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the BatchTradeExtension
*/
function removeExtension() external override {
IDelegatedManager delegatedManager = IDelegatedManager(msg.sender);
ISetToken setToken = delegatedManager.setToken();
_removeExtension(setToken, delegatedManager);
}
/**
* ONLY OPERATOR: Executes a batch of trades on a supported DEX. If any individual trades fail, events are emitted.
* @dev Although the SetToken units are passed in for the send and receive quantities, the total quantity
* sent and received is the quantity of component units multiplied by the SetToken totalSupply.
*
* @param _setToken Instance of the SetToken to trade
* @param _trades Array of TradeInfo structs containing information about trades
*/
function batchTrade(
ISetToken _setToken,
TradeInfo[] memory _trades
)
external
onlyOperator(_setToken)
{
uint256 tradesLength = _trades.length;
IDelegatedManager manager = _manager(_setToken);
for(uint256 i = 0; i < tradesLength; i++) {
require(isIntegration[_trades[i].exchangeName], "Must be allowed integration");
require(manager.isAllowedAsset(_trades[i].receiveToken), "Must be allowed asset");
bytes memory callData = abi.encodeWithSelector(
ITradeModule.trade.selector,
_setToken,
_trades[i].exchangeName,
_trades[i].sendToken,
_trades[i].sendQuantity,
_trades[i].receiveToken,
_trades[i].receiveQuantity,
_trades[i].data
);
// ZeroEx (for example) throws custom errors which slip through OpenZeppelin's
// functionCallWithValue error management and surface here as `bytes`. These should be
// decode-able off-chain given enough context about protocol targeted by the adapter.
try manager.interactManager(address(tradeModule), callData) {}
catch Error(string memory reason) {
emit StringTradeFailed(
address(_setToken),
i,
reason,
_trades[i]
);
} catch (bytes memory lowLevelData) {
emit BytesTradeFailed(
address(_setToken),
i,
lowLevelData,
_trades[i]
);
}
}
}
/* ============ External Getter Functions ============ */
function getIntegrations() external view returns (string[] memory) {
return integrations;
}
/* ============ Internal Functions ============ */
/**
* Add an allowed TradeModule exchange integration to the BatchTradeExtension
*
* @param _integrationName Name of TradeModule exchange integration to allow
*/
function _addIntegration(string memory _integrationName) internal {
isIntegration[_integrationName] = true;
emit IntegrationAdded(_integrationName);
}
/**
* Internal function to initialize TradeModule on the SetToken associated with the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
*/
function _initializeModule(ISetToken _setToken, IDelegatedManager _delegatedManager) internal {
bytes memory callData = abi.encodeWithSelector(ITradeModule.initialize.selector, _setToken);
_invokeManager(_delegatedManager, address(tradeModule), callData);
}
}
GlobalClaimExtension.sol 742 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IAirdropModule } from "../interfaces/IAirdropModule.sol";
import { IClaimAdapter } from "../interfaces/IClaimAdapter.sol";
import { IClaimModule } from "../interfaces/IClaimModule.sol";
import { IIntegrationRegistry } from "../interfaces/IIntegrationRegistry.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
/**
* @title GlobalClaimExtension
* @author Set Protocol
*
* Smart contract global extension which provides DelegatedManager owner the ability to perform administrative tasks on the AirdropModule
* and the ClaimModule and the DelegatedManager operator(s) the ability to
* - absorb tokens sent to the SetToken into the token's positions
* - claim tokens from external protocols given to a Set as part of participating in incentivized activities of other protocols
* and absorb them into the SetToken's positions in a single transaction
*/
contract GlobalClaimExtension is BaseGlobalExtension {
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Events ============ */
event ClaimExtensionInitialized(
address indexed _setToken,
address indexed _delegatedManager
);
event FeesDistributed(
address _setToken, // Address of SetToken which generated the airdrop fees
address _token, // Address of the token to distribute
address indexed _ownerFeeRecipient, // Address which receives the owner's take of the fees
address indexed _methodologist, // Address of methodologist
uint256 _ownerTake, // Amount of _token distributed to owner
uint256 _methodologistTake // Amount of _token distributed to methodologist
);
/* ============ Modifiers ============ */
/**
* Throws if useAssetAllowList is true and one of the assets is not on the asset allow list
*/
modifier onlyAllowedAssets(ISetToken _setToken, address[] memory _assets) {
_validateAllowedAssets(_setToken, _assets);
_;
}
/**
* Throws if anyoneAbsorb on the AirdropModule is false and caller is not the operator
*/
modifier onlyValidAbsorbCaller(ISetToken _setToken) {
require(_isValidAbsorbCaller(_setToken), "Must be valid AirdropModule absorb caller");
_;
}
/**
* Throws if caller is not the operator and either anyoneAbsorb on the AirdropModule or anyoneClaim on the ClaimModule is false
*/
modifier onlyValidClaimAndAbsorbCaller(ISetToken _setToken) {
require(_isValidClaimAndAbsorbCaller(_setToken), "Must be valid AirdropModule absorb and ClaimModule claim caller");
_;
}
/* ============ State Variables ============ */
// Instance of AirdropModule
IAirdropModule public immutable airdropModule;
// Instance of ClaimModule
IClaimModule public immutable claimModule;
// Instance of IntegrationRegistry
IIntegrationRegistry public immutable integrationRegistry;
/* ============ Constructor ============ */
/**
* Instantiate with ManagerCore, AirdropModule, ClaimModule, and Controller addresses.
*
* @param _managerCore Address of ManagerCore contract
* @param _airdropModule Address of AirdropModule contract
* @param _claimModule Address of ClaimModule contract
* @param _integrationRegistry Address of IntegrationRegistry contract
*/
constructor(
IManagerCore _managerCore,
IAirdropModule _airdropModule,
IClaimModule _claimModule,
IIntegrationRegistry _integrationRegistry
)
public
BaseGlobalExtension(_managerCore)
{
airdropModule = _airdropModule;
claimModule = _claimModule;
integrationRegistry = _integrationRegistry;
}
/* ============ External Functions ============ */
/**
* ANYONE CALLABLE: Distributes airdrop fees accrued to the DelegatedManager. Calculates fees for
* owner and methodologist, and sends to owner fee recipient and methodologist respectively.
*
* @param _setToken Address of SetToken
* @param _token Address of token to distribute
*/
function distributeFees(
ISetToken _setToken,
IERC20 _token
)
public
{
IDelegatedManager delegatedManager = _manager(_setToken);
uint256 totalFees = _token.balanceOf(address(delegatedManager));
address methodologist = delegatedManager.methodologist();
address ownerFeeRecipient = delegatedManager.ownerFeeRecipient();
uint256 ownerTake = totalFees.preciseMul(delegatedManager.ownerFeeSplit());
uint256 methodologistTake = totalFees.sub(ownerTake);
if (ownerTake > 0) {
delegatedManager.transferTokens(address(_token), ownerFeeRecipient, ownerTake);
}
if (methodologistTake > 0) {
delegatedManager.transferTokens(address(_token), methodologist, methodologistTake);
}
emit FeesDistributed(
address(_setToken),
address(_token),
ownerFeeRecipient,
methodologist,
ownerTake,
methodologistTake
);
}
/**
* ONLY OWNER: Initializes AirdropModule on the SetToken associated with the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize the AirdropModule for
* @param _airdropSettings Struct of airdrop setting for Set including accepted airdrops, feeRecipient,
* airdropFee, and indicating if anyone can call an absorb
*/
function initializeAirdropModule(
IDelegatedManager _delegatedManager,
IAirdropModule.AirdropSettings memory _airdropSettings
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
_initializeAirdropModule(_delegatedManager.setToken(), _delegatedManager, _airdropSettings);
}
/**
* ONLY OWNER: Initializes ClaimModule on the SetToken associated with the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize the ClaimModule for
* @param _anyoneClaim Boolean indicating if anyone can claim or just manager
* @param _rewardPools Addresses of rewardPools that identifies the contract governing claims. Maps to same index integrationNames
* @param _integrationNames Human-readable names matching adapter used to collect claim on pool. Maps to same index in rewardPools
*/
function initializeClaimModule(
IDelegatedManager _delegatedManager,
bool _anyoneClaim,
address[] calldata _rewardPools,
string[] calldata _integrationNames
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
_initializeClaimModule(_delegatedManager.setToken(), _delegatedManager, _anyoneClaim, _rewardPools, _integrationNames);
}
/**
* ONLY OWNER: Initializes ClaimExtension to the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
emit ClaimExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY OWNER: Initializes ClaimExtension to the DelegatedManager and AirdropModule and ClaimModule to the SetToken
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
* @param _airdropSettings Struct of airdrop setting for Set including accepted airdrops, feeRecipient,
* airdropFee, and indicating if anyone can call an absorb
* @param _anyoneClaim Boolean indicating if anyone can claim or just manager
* @param _rewardPools Addresses of rewardPools that identifies the contract governing claims. Maps to same index integrationNames
* @param _integrationNames Human-readable names matching adapter used to collect claim on pool. Maps to same index in rewardPools
*/
function initializeModulesAndExtension(
IDelegatedManager _delegatedManager,
IAirdropModule.AirdropSettings memory _airdropSettings,
bool _anyoneClaim,
address[] calldata _rewardPools,
string[] calldata _integrationNames
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
_initializeAirdropModule(_delegatedManager.setToken(), _delegatedManager, _airdropSettings);
_initializeClaimModule(_delegatedManager.setToken(), _delegatedManager, _anyoneClaim, _rewardPools, _integrationNames);
emit ClaimExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the ClaimExtension
*/
function removeExtension() external override {
IDelegatedManager delegatedManager = IDelegatedManager(msg.sender);
ISetToken setToken = delegatedManager.setToken();
_removeExtension(setToken, delegatedManager);
}
/**
* ONLY VALID ABSORB CALLER: Absorb passed tokens into respective positions. If airdropFee defined, send portion to feeRecipient
* and portion to protocol feeRecipient address. Callable only by operator unless set anyoneAbsorb is true on the AirdropModule.
*
* @param _setToken Address of SetToken
* @param _tokens Array of tokens to absorb
*/
function batchAbsorb(
ISetToken _setToken,
address[] memory _tokens
)
external
onlyValidAbsorbCaller(_setToken)
onlyAllowedAssets(_setToken, _tokens)
{
_batchAbsorb(_setToken, _tokens);
}
/**
* ONLY VALID ABSORB CALLER: Absorb specified token into position. If airdropFee defined, send portion to feeRecipient and portion to
* protocol feeRecipient address. Callable only by operator unless anyoneAbsorb is true on the AirdropModule.
*
* @param _setToken Address of SetToken
* @param _token Address of token to absorb
*/
function absorb(
ISetToken _setToken,
IERC20 _token
)
external
onlyValidAbsorbCaller(_setToken)
onlyAllowedAsset(_setToken, address(_token))
{
_absorb(_setToken, _token);
}
/**
* ONLY OWNER: Adds new tokens to be added to positions when absorb is called.
*
* @param _setToken Address of SetToken
* @param _airdrop Component to add to airdrop list
*/
function addAirdrop(
ISetToken _setToken,
IERC20 _airdrop
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IAirdropModule.addAirdrop.selector,
_setToken,
_airdrop
);
_invokeManager(_manager(_setToken), address(airdropModule), callData);
}
/**
* ONLY OWNER: Removes tokens from list to be absorbed.
*
* @param _setToken Address of SetToken
* @param _airdrop Component to remove from airdrop list
*/
function removeAirdrop(
ISetToken _setToken,
IERC20 _airdrop
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IAirdropModule.removeAirdrop.selector,
_setToken,
_airdrop
);
_invokeManager(_manager(_setToken), address(airdropModule), callData);
}
/**
* ONLY OWNER: Update whether manager allows other addresses to call absorb.
*
* @param _setToken Address of SetToken
*/
function updateAnyoneAbsorb(
ISetToken _setToken,
bool _anyoneAbsorb
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IAirdropModule.updateAnyoneAbsorb.selector,
_setToken,
_anyoneAbsorb
);
_invokeManager(_manager(_setToken), address(airdropModule), callData);
}
/**
* ONLY OWNER: Update address AirdropModule manager fees are sent to.
*
* @param _setToken Address of SetToken
* @param _newFeeRecipient Address of new fee recipient
*/
function updateAirdropFeeRecipient(
ISetToken _setToken,
address _newFeeRecipient
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IAirdropModule.updateFeeRecipient.selector,
_setToken,
_newFeeRecipient
);
_invokeManager(_manager(_setToken), address(airdropModule), callData);
}
/**
* ONLY OWNER: Update airdrop fee percentage.
*
* @param _setToken Address of SetToken
* @param _newFee Percentage, in preciseUnits, of new airdrop fee (1e16 = 1%)
*/
function updateAirdropFee(
ISetToken _setToken,
uint256 _newFee
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IAirdropModule.updateAirdropFee.selector,
_setToken,
_newFee
);
_invokeManager(_manager(_setToken), address(airdropModule), callData);
}
/**
* ONLY VALID CLAIM AND ABSORB CALLER: Claim the rewards available on the rewardPool for the specified claim integration and absorb
* the reward token into position. If airdropFee defined, send portion to feeRecipient and portion to protocol feeRecipient address.
* Callable only by operator unless anyoneAbsorb on the AirdropModule and anyoneClaim on the ClaimModule are true.
*
* @param _setToken Address of SetToken
* @param _rewardPool Address of the rewardPool that identifies the contract governing claims
* @param _integrationName ID of claim module integration (mapping on integration registry)
*/
function claimAndAbsorb(
ISetToken _setToken,
address _rewardPool,
string calldata _integrationName
)
external
onlyValidClaimAndAbsorbCaller(_setToken)
{
IERC20 rewardsToken = _getAndValidateRewardsToken(_setToken, _rewardPool, _integrationName);
_claim(_setToken, _rewardPool, _integrationName);
_absorb(_setToken, rewardsToken);
}
/**
* ONLY VALID CLAIM AND ABSORB CALLER: Claims rewards on all the passed rewardPool/claim integration pairs and absorb the reward tokens
* into positions. If airdropFee defined, send portion of each reward token to feeRecipient and a portion to protocol feeRecipient address.
* Callable only by operator unless anyoneAbsorb on the AirdropModule and anyoneClaim on the ClaimModule are true.
*
* @param _setToken Address of SetToken
* @param _rewardPools Addresses of rewardPools that identifies the contract governing claims. Maps to same index integrationNames
* @param _integrationNames Human-readable names matching adapter used to collect claim on pool. Maps to same index in rewardPools
*/
function batchClaimAndAbsorb(
ISetToken _setToken,
address[] calldata _rewardPools,
string[] calldata _integrationNames
)
external
onlyValidClaimAndAbsorbCaller(_setToken)
{
address[] storage rewardsTokens;
uint256 numPools = _rewardPools.length;
for (uint256 i = 0; i < numPools; i++) {
IERC20 token = _getAndValidateRewardsToken(_setToken, _rewardPools[i], _integrationNames[i]);
rewardsTokens.push(address(token));
}
_batchClaim(_setToken, _rewardPools, _integrationNames);
_batchAbsorb(_setToken, rewardsTokens);
}
/**
* ONLY OWNER: Update whether manager allows other addresses to call claim.
*
* @param _setToken Address of SetToken
*/
function updateAnyoneClaim(
ISetToken _setToken,
bool _anyoneClaim
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IClaimModule.updateAnyoneClaim.selector,
_setToken,
_anyoneClaim
);
_invokeManager(_manager(_setToken), address(claimModule), callData);
}
/**
* ONLY OWNER: Adds a new claim integration for an existent rewardPool. If rewardPool doesn't have existing
* claims then rewardPool is added to rewardPoolList. The claim integration is associated to an adapter that
* provides the functionality to claim the rewards for a specific token.
*
* @param _setToken Address of SetToken
* @param _rewardPool Address of the rewardPool that identifies the contract governing claims
* @param _integrationName ID of claim module integration (mapping on integration registry)
*/
function addClaim(
ISetToken _setToken,
address _rewardPool,
string calldata _integrationName
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IClaimModule.addClaim.selector,
_setToken,
_rewardPool,
_integrationName
);
_invokeManager(_manager(_setToken), address(claimModule), callData);
}
/**
* ONLY OWNER: Adds a new rewardPool to the list to perform claims for the SetToken indicating the list of
* claim integrations. Each claim integration is associated to an adapter that provides the functionality to claim
* the rewards for a specific token.
*
* @param _setToken Address of SetToken
* @param _rewardPools Addresses of rewardPools that identifies the contract governing claims. Maps to same
* index integrationNames
* @param _integrationNames Human-readable names matching adapter used to collect claim on pool. Maps to same index
* in rewardPools
*/
function batchAddClaim(
ISetToken _setToken,
address[] calldata _rewardPools,
string[] calldata _integrationNames
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IClaimModule.batchAddClaim.selector,
_setToken,
_rewardPools,
_integrationNames
);
_invokeManager(_manager(_setToken), address(claimModule), callData);
}
/**
* ONLY OWNER: Removes a claim integration from an existent rewardPool. If no claim remains for reward pool then
* reward pool is removed from rewardPoolList.
*
* @param _setToken Address of SetToken
* @param _rewardPool Address of the rewardPool that identifies the contract governing claims
* @param _integrationName ID of claim module integration (mapping on integration registry)
*/
function removeClaim(
ISetToken _setToken,
address _rewardPool,
string calldata _integrationName
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IClaimModule.removeClaim.selector,
_setToken,
_rewardPool,
_integrationName
);
_invokeManager(_manager(_setToken), address(claimModule), callData);
}
/**
* ONLY OWNER: Batch removes claims from SetToken's settings.
*
* @param _setToken Address of SetToken
* @param _rewardPools Addresses of rewardPools that identifies the contract governing claims. Maps to same index
* integrationNames
* @param _integrationNames Human-readable names matching adapter used to collect claim on pool. Maps to same index in
* rewardPools
*/
function batchRemoveClaim(
ISetToken _setToken,
address[] calldata _rewardPools,
string[] calldata _integrationNames
)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSelector(
IClaimModule.batchRemoveClaim.selector,
_setToken,
_rewardPools,
_integrationNames
);
_invokeManager(_manager(_setToken), address(claimModule), callData);
}
/* ============ Internal Functions ============ */
/**
* Internal function to initialize AirdropModule on the SetToken associated with the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize the AirdropModule for
* @param _airdropSettings Struct of airdrop setting for Set including accepted airdrops, feeRecipient,
* airdropFee, and indicating if anyone can call an absorb
*/
function _initializeAirdropModule(
ISetToken _setToken,
IDelegatedManager _delegatedManager,
IAirdropModule.AirdropSettings memory _airdropSettings
)
internal
{
bytes memory callData = abi.encodeWithSelector(
IAirdropModule.initialize.selector,
_setToken,
_airdropSettings
);
_invokeManager(_delegatedManager, address(airdropModule), callData);
}
/**
* Internal function to initialize ClaimModule on the SetToken associated with the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize the ClaimModule for
* @param _anyoneClaim Boolean indicating if anyone can claim or just manager
* @param _rewardPools Addresses of rewardPools that identifies the contract governing claims. Maps to same index integrationNames
* @param _integrationNames Human-readable names matching adapter used to collect claim on pool. Maps to same index in rewardPools
*/
function _initializeClaimModule(
ISetToken _setToken,
IDelegatedManager _delegatedManager,
bool _anyoneClaim,
address[] calldata _rewardPools,
string[] calldata _integrationNames
)
internal
{
bytes memory callData = abi.encodeWithSelector(
IClaimModule.initialize.selector,
_setToken,
_anyoneClaim,
_rewardPools,
_integrationNames
);
_invokeManager(_delegatedManager, address(claimModule), callData);
}
/**
* Must have all assets on asset allow list or useAssetAllowlist to be false
*/
function _validateAllowedAssets(ISetToken _setToken, address[] memory _assets) internal view {
IDelegatedManager manager = _manager(_setToken);
if (manager.useAssetAllowlist()) {
uint256 assetsLength = _assets.length;
for (uint i = 0; i < assetsLength; i++) {
require(manager.assetAllowlist(_assets[i]), "Must be allowed asset");
}
}
}
/**
* AirdropModule anyoneAbsorb setting must be true or must be operator
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
*/
function _isValidAbsorbCaller(ISetToken _setToken) internal view returns(bool) {
return airdropModule.airdropSettings(_setToken).anyoneAbsorb || _manager(_setToken).operatorAllowlist(msg.sender);
}
/**
* Must be operator or must have both AirdropModule anyoneAbsorb and ClaimModule anyoneClaim
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
*/
function _isValidClaimAndAbsorbCaller(ISetToken _setToken) internal view returns(bool) {
return (
(claimModule.anyoneClaim(_setToken) && airdropModule.airdropSettings(_setToken).anyoneAbsorb)
|| _manager(_setToken).operatorAllowlist(msg.sender)
);
}
/**
* Absorb specified token into position. If airdropFee defined, send portion to feeRecipient and portion to protocol feeRecipient address.
*
* @param _setToken Address of SetToken
* @param _token Address of token to absorb
*/
function _absorb(ISetToken _setToken, IERC20 _token) internal {
bytes memory callData = abi.encodeWithSelector(
IAirdropModule.absorb.selector,
_setToken,
_token
);
_invokeManager(_manager(_setToken), address(airdropModule), callData);
}
/**
* Absorb passed tokens into respective positions. If airdropFee defined, send portion to feeRecipient and portion to protocol feeRecipient address.
*
* @param _setToken Address of SetToken
* @param _tokens Array of tokens to absorb
*/
function _batchAbsorb(ISetToken _setToken, address[] memory _tokens) internal {
bytes memory callData = abi.encodeWithSelector(
IAirdropModule.batchAbsorb.selector,
_setToken,
_tokens
);
_invokeManager(_manager(_setToken), address(airdropModule), callData);
}
/**
* Claim the rewards available on the rewardPool for the specified claim integration and absorb the reward token into position.
*
* @param _setToken Address of SetToken
* @param _rewardPool Address of the rewardPool that identifies the contract governing claims
* @param _integrationName ID of claim module integration (mapping on integration registry)
*/
function _claim(ISetToken _setToken, address _rewardPool, string calldata _integrationName) internal {
bytes memory callData = abi.encodeWithSelector(
IClaimModule.claim.selector,
_setToken,
_rewardPool,
_integrationName
);
_invokeManager(_manager(_setToken), address(claimModule), callData);
}
/**
* Claims rewards on all the passed rewardPool/claim integration pairs and absorb the reward tokens into positions.
*
* @param _setToken Address of SetToken
* @param _rewardPools Addresses of rewardPools that identifies the contract governing claims. Maps to same index integrationNames
* @param _integrationNames Human-readable names matching adapter used to collect claim on pool. Maps to same index in rewardPools
*/
function _batchClaim(ISetToken _setToken, address[] calldata _rewardPools, string[] calldata _integrationNames) internal {
bytes memory callData = abi.encodeWithSelector(
IClaimModule.batchClaim.selector,
_setToken,
_rewardPools,
_integrationNames
);
_invokeManager(_manager(_setToken), address(claimModule), callData);
}
/**
* Get the rewards token from the rewardPool and integrationName and check if it is an allowed asset on the DelegatedManager
*
* @param _setToken Address of SetToken
* @param _rewardPool Address of the rewardPool that identifies the contract governing claims
* @param _integrationName ID of claim module integration (mapping on integration registry)
*/
function _getAndValidateRewardsToken(ISetToken _setToken, address _rewardPool, string calldata _integrationName) internal view returns(IERC20) {
IClaimAdapter adapter = IClaimAdapter(integrationRegistry.getIntegrationAdapter(address(claimModule), _integrationName));
IERC20 rewardsToken = adapter.getTokenAddress(_rewardPool);
require(_manager(_setToken).isAllowedAsset(address(rewardsToken)), "Must be allowed asset");
return rewardsToken;
}
}
GlobalIssuanceExtension.sol 284 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IIssuanceModule } from "../interfaces/IIssuanceModule.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
/**
* @title GlobalIssuanceExtension
* @author Set Protocol
*
* Smart contract global extension which provides DelegatedManager owner and methodologist the ability to accrue and split
* issuance and redemption fees. Owner may configure the fee split percentages.
*
* Notes
* - the fee split is set on the Delegated Manager contract
* - when fees distributed via this contract will be inclusive of all fee types that have already been accrued
*/
contract GlobalIssuanceExtension is BaseGlobalExtension {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Events ============ */
event IssuanceExtensionInitialized(
address indexed _setToken,
address indexed _delegatedManager
);
event FeesDistributed(
address _setToken,
address indexed _ownerFeeRecipient,
address indexed _methodologist,
uint256 _ownerTake,
uint256 _methodologistTake
);
/* ============ State Variables ============ */
// Instance of IssuanceModule
IIssuanceModule public immutable issuanceModule;
/* ============ Constructor ============ */
constructor(
IManagerCore _managerCore,
IIssuanceModule _issuanceModule
)
public
BaseGlobalExtension(_managerCore)
{
issuanceModule = _issuanceModule;
}
/* ============ External Functions ============ */
/**
* ANYONE CALLABLE: Distributes fees accrued to the DelegatedManager. Calculates fees for
* owner and methodologist, and sends to owner fee recipient and methodologist respectively.
*/
function distributeFees(ISetToken _setToken) public {
IDelegatedManager delegatedManager = _manager(_setToken);
uint256 totalFees = _setToken.balanceOf(address(delegatedManager));
address methodologist = delegatedManager.methodologist();
address ownerFeeRecipient = delegatedManager.ownerFeeRecipient();
uint256 ownerTake = totalFees.preciseMul(delegatedManager.ownerFeeSplit());
uint256 methodologistTake = totalFees.sub(ownerTake);
if (ownerTake > 0) {
delegatedManager.transferTokens(address(_setToken), ownerFeeRecipient, ownerTake);
}
if (methodologistTake > 0) {
delegatedManager.transferTokens(address(_setToken), methodologist, methodologistTake);
}
emit FeesDistributed(address(_setToken), ownerFeeRecipient, methodologist, ownerTake, methodologistTake);
}
/**
* ONLY OWNER: Initializes IssuanceModule on the SetToken associated with the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize the IssuanceModule for
* @param _maxManagerFee Maximum fee that can be charged on issue and redeem
* @param _managerIssueFee Fee to charge on issuance
* @param _managerRedeemFee Fee to charge on redemption
* @param _feeRecipient Address to send fees to
* @param _managerIssuanceHook Instance of the contract with the Pre-Issuance Hook function
*/
function initializeModule(
IDelegatedManager _delegatedManager,
uint256 _maxManagerFee,
uint256 _managerIssueFee,
uint256 _managerRedeemFee,
address _feeRecipient,
address _managerIssuanceHook
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
require(_delegatedManager.isInitializedExtension(address(this)), "Extension must be initialized");
_initializeModule(
_delegatedManager.setToken(),
_delegatedManager,
_maxManagerFee,
_managerIssueFee,
_managerRedeemFee,
_feeRecipient,
_managerIssuanceHook
);
}
/**
* ONLY OWNER: Initializes IssuanceExtension to the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
emit IssuanceExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY OWNER: Initializes IssuanceExtension to the DelegatedManager and IssuanceModule to the SetToken
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
* @param _maxManagerFee Maximum fee that can be charged on issue and redeem
* @param _managerIssueFee Fee to charge on issuance
* @param _managerRedeemFee Fee to charge on redemption
* @param _feeRecipient Address to send fees to
* @param _managerIssuanceHook Instance of the contract with the Pre-Issuance Hook function
*/
function initializeModuleAndExtension(
IDelegatedManager _delegatedManager,
uint256 _maxManagerFee,
uint256 _managerIssueFee,
uint256 _managerRedeemFee,
address _feeRecipient,
address _managerIssuanceHook
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
_initializeModule(
setToken,
_delegatedManager,
_maxManagerFee,
_managerIssueFee,
_managerRedeemFee,
_feeRecipient,
_managerIssuanceHook
);
emit IssuanceExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the IssuanceExtension
*/
function removeExtension() external override {
IDelegatedManager delegatedManager = IDelegatedManager(msg.sender);
ISetToken setToken = delegatedManager.setToken();
_removeExtension(setToken, delegatedManager);
}
/**
* ONLY OWNER: Updates issuance fee on IssuanceModule.
*
* @param _setToken Instance of the SetToken to update issue fee for
* @param _newFee New issue fee percentage in precise units (1% = 1e16, 100% = 1e18)
*/
function updateIssueFee(ISetToken _setToken, uint256 _newFee)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSignature("updateIssueFee(address,uint256)", _setToken, _newFee);
_invokeManager(_manager(_setToken), address(issuanceModule), callData);
}
/**
* ONLY OWNER: Updates redemption fee on IssuanceModule.
*
* @param _setToken Instance of the SetToken to update redeem fee for
* @param _newFee New redeem fee percentage in precise units (1% = 1e16, 100% = 1e18)
*/
function updateRedeemFee(ISetToken _setToken, uint256 _newFee)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSignature("updateRedeemFee(address,uint256)", _setToken, _newFee);
_invokeManager(_manager(_setToken), address(issuanceModule), callData);
}
/**
* ONLY OWNER: Updates fee recipient on IssuanceModule
*
* @param _setToken Instance of the SetToken to update fee recipient for
* @param _newFeeRecipient Address of new fee recipient. This should be the address of the DelegatedManager
*/
function updateFeeRecipient(ISetToken _setToken, address _newFeeRecipient)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSignature("updateFeeRecipient(address,address)", _setToken, _newFeeRecipient);
_invokeManager(_manager(_setToken), address(issuanceModule), callData);
}
/* ============ Internal Functions ============ */
/**
* Internal function to initialize IssuanceModule on the SetToken associated with the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
* @param _maxManagerFee Maximum fee that can be charged on issue and redeem
* @param _managerIssueFee Fee to charge on issuance
* @param _managerRedeemFee Fee to charge on redemption
* @param _feeRecipient Address to send fees to
* @param _managerIssuanceHook Instance of the contract with the Pre-Issuance Hook function
*/
function _initializeModule(
ISetToken _setToken,
IDelegatedManager _delegatedManager,
uint256 _maxManagerFee,
uint256 _managerIssueFee,
uint256 _managerRedeemFee,
address _feeRecipient,
address _managerIssuanceHook
)
internal
{
bytes memory callData = abi.encodeWithSignature(
"initialize(address,uint256,uint256,uint256,address,address)",
_setToken,
_maxManagerFee,
_managerIssueFee,
_managerRedeemFee,
_feeRecipient,
_managerIssuanceHook
);
_invokeManager(_delegatedManager, address(issuanceModule), callData);
}
}
GlobalOptimisticAuctionRebalanceExtension.sol 360 lines
/*
Copyright 2023 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IAuctionRebalanceModuleV1 } from "../interfaces/IAuctionRebalanceModuleV1.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import {GlobalAuctionRebalanceExtension} from "./GlobalAuctionRebalanceExtension.sol";
import {AncillaryData } from "../lib/AncillaryData.sol";
import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Interface.sol";
/**
* @title GlobalOptimisticAuctionRebalanceExtension
* @author Index Coop
*
* @dev The contract extends `GlobalAuctionRebalanceExtension` by adding an optimistic oracle mechanism for validating rules on the proposing and executing of rebalances.
* It allows setting product-specific parameters for optimistic rebalancing and includes callback functions for resolved or disputed assertions.
*/
contract GlobalOptimisticAuctionRebalanceExtension is GlobalAuctionRebalanceExtension {
using AddressArrayUtils for address[];
using SafeERC20 for IERC20;
/* ============ Events ============ */
event ProductSettingsUpdated(
IERC20 indexed setToken,
address indexed delegatedManager,
OptimisticRebalanceParams optimisticParams,
bytes32 indexed rulesHash
);
event RebalanceProposed(
ISetToken indexed setToken,
IERC20 indexed quoteAsset,
address[] oldComponents,
address[] newComponents,
AuctionExecutionParams[] newComponentsAuctionParams,
AuctionExecutionParams[] oldComponentsAuctionParams,
bool shouldLockSetToken,
uint256 rebalanceDuration,
uint256 positionMultiplier
);
event AssertedClaim(
IERC20 indexed _setToken,
address indexed _assertedBy,
bytes32 indexed rulesHash,
bytes32 _assertionId,
bytes _claimData
);
event ProposalDeleted(
bytes32 assertionID,
Proposal indexed proposal
);
/* ============ Structs ============ */
struct AuctionExtensionParams {
IManagerCore managerCore; // Registry contract for governance approved GlobalExtensions, DelegatedManagerFactories, and DelegatedManagers.
IAuctionRebalanceModuleV1 auctionModule; // Contract that rebalances index sets via single-asset auctions
}
struct OptimisticRebalanceParams{
IERC20 collateral; // Collateral currency used to assert proposed transactions.
uint64 liveness; // The amount of time to dispute proposed transactions before they can be executed.
uint256 bondAmount; // Configured amount of collateral currency to make assertions for proposed transactions.
bytes32 identifier; // Identifier used to request price from the DVM.
OptimisticOracleV3Interface optimisticOracleV3; // Optimistic Oracle V3 contract used to assert proposed transactions.
}
struct ProductSettings{
OptimisticRebalanceParams optimisticParams; // OptimisticRebalanceParams struct containing optimistic rebalance parameters.
bytes32 rulesHash; // IPFS hash of the rules for the product.
}
struct Proposal{
bytes32 proposalHash; // Hash of the proposal.
ISetToken product; // Address of the SetToken to set rules and settings for.
}
/* ============ State Variables ============ */
mapping (ISetToken=>ProductSettings) public productSettings; // Mapping of set token to ProductSettings
mapping(bytes32 => bytes32) public assertionIds; // Maps proposal hashes to assertionIds.
mapping(bytes32 => Proposal) public proposedProduct; // Maps assertionIds to a Proposal.
// Keys for assertion claim data.
bytes public constant PROPOSAL_HASH_KEY = "proposalHash";
bytes public constant RULES_KEY = "rulesIPFSHash";
/* ============ Constructor ============ */
/*
* @dev Initializes the GlobalOptimisticAuctionRebalanceExtension with the passed parameters.
*
* @param _auctionParams AuctionExtensionParams struct containing the managerCore and auctionModule addresses.
*/
constructor(AuctionExtensionParams memory _auctionParams) public GlobalAuctionRebalanceExtension(_auctionParams.managerCore, _auctionParams.auctionModule) {
}
/* ============ External Functions ============ */
/**
* @dev OPERATOR ONLY: sets product settings for a given set token
* @param _product Address of the SetToken to set rules and settings for.
* @param _optimisticParams OptimisticRebalanceParams struct containing optimistic rebalance parameters.
* @param _rulesHash bytes32 containing the ipfs hash rules for the product.
*/
function setProductSettings(
ISetToken _product,
OptimisticRebalanceParams memory _optimisticParams,
bytes32 _rulesHash
)
external
onlyOperator(_product)
{
productSettings[_product] = ProductSettings({
optimisticParams: _optimisticParams,
rulesHash: _rulesHash
});
emit ProductSettingsUpdated(_product, _product.manager(), _optimisticParams, _rulesHash);
}
/**
* @param _quoteAsset ERC20 token used as the quote asset in auctions.
* @param _oldComponents Addresses of existing components in the SetToken.
* @param _newComponents Addresses of new components to be added.
* @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents.
* @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to
* the current component positions. Set to 0 for components being removed.
* @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken.
* @param _rebalanceDuration Duration of the rebalance in seconds.
* @param _positionMultiplier Position multiplier at the time target units were calculated.
*/
function proposeRebalance(
ISetToken _setToken,
IERC20 _quoteAsset,
address[] memory _oldComponents,
address[] memory _newComponents,
AuctionExecutionParams[] memory _newComponentsAuctionParams,
AuctionExecutionParams[] memory _oldComponentsAuctionParams,
bool _shouldLockSetToken,
uint256 _rebalanceDuration,
uint256 _positionMultiplier
)
external
{
bytes32 proposalHash = keccak256(abi.encode(
_setToken,
_quoteAsset,
_oldComponents,
_newComponents,
_newComponentsAuctionParams,
_oldComponentsAuctionParams,
_shouldLockSetToken,
_rebalanceDuration,
_positionMultiplier
));
ProductSettings memory settings = productSettings[_setToken];
require(assertionIds[proposalHash] == bytes32(0), "Proposal already exists");
require(settings.rulesHash != bytes32(""), "Rules not set");
require(address(settings.optimisticParams.optimisticOracleV3) != address(0), "Oracle not set");
bytes memory claim = _constructClaim(proposalHash, settings.rulesHash);
uint256 totalBond = _pullBond(settings.optimisticParams);
bytes32 assertionId = settings.optimisticParams.optimisticOracleV3.assertTruth(
claim,
msg.sender,
address(this),
address(0),
settings.optimisticParams.liveness,
settings.optimisticParams.collateral,
totalBond,
settings.optimisticParams.identifier,
bytes32(0)
);
assertionIds[proposalHash] = assertionId;
proposedProduct[assertionId] = Proposal({
proposalHash: proposalHash,
product: _setToken
});
emit RebalanceProposed( _setToken, _quoteAsset, _oldComponents, _newComponents, _newComponentsAuctionParams, _oldComponentsAuctionParams, _shouldLockSetToken, _rebalanceDuration, _positionMultiplier);
emit AssertedClaim(_setToken, msg.sender, settings.rulesHash, assertionId, claim);
}
/**
* @dev OPERATOR ONLY: Checks that the old components array matches the current components array and then invokes the
* AuctionRebalanceModuleV1 startRebalance function.
*
* Refer to AuctionRebalanceModuleV1 for function specific restrictions.
*
* @param _quoteAsset ERC20 token used as the quote asset in auctions.
* @param _oldComponents Addresses of existing components in the SetToken.
* @param _newComponents Addresses of new components to be added.
* @param _newComponentsAuctionParams AuctionExecutionParams for new components, indexed corresponding to _newComponents.
* @param _oldComponentsAuctionParams AuctionExecutionParams for existing components, indexed corresponding to
* the current component positions. Set to 0 for components being removed.
* @param _shouldLockSetToken Indicates if the rebalance should lock the SetToken.
* @param _rebalanceDuration Duration of the rebalance in seconds.
* @param _positionMultiplier Position multiplier at the time target units were calculated.
*/
function startRebalance(
ISetToken _setToken,
IERC20 _quoteAsset,
address[] memory _oldComponents,
address[] memory _newComponents,
AuctionExecutionParams[] memory _newComponentsAuctionParams,
AuctionExecutionParams[] memory _oldComponentsAuctionParams,
bool _shouldLockSetToken,
uint256 _rebalanceDuration,
uint256 _positionMultiplier
)
external
override
{
bytes32 proposalHash = keccak256(abi.encode(
_setToken,
_quoteAsset,
_oldComponents,
_newComponents,
_newComponentsAuctionParams,
_oldComponentsAuctionParams,
_shouldLockSetToken,
_rebalanceDuration,
_positionMultiplier
));
bytes32 assertionId = assertionIds[proposalHash];
// Disputed assertions are expected to revert here. Assumption past this point is that there was a valid assertion.
require(assertionId != bytes32(0), "Proposal hash does not exist");
ProductSettings memory settings = productSettings[_setToken];
_deleteProposal(assertionId);
// There is no need to check the assertion result as this point can be reached only for non-disputed assertions.
// It is expected that future versions of the Optimistic Oracle will always revert here,
// if the assertionId has not been settled and can not currently be settled.
settings.optimisticParams.optimisticOracleV3.settleAndGetAssertionResult(assertionId);
address[] memory currentComponents = _setToken.getComponents();
require(currentComponents.length == _oldComponents.length, "Mismatch: old and current components length");
for (uint256 i = 0; i < _oldComponents.length; i++) {
require(currentComponents[i] == _oldComponents[i], "Mismatch: old and current components");
}
bytes memory callData = abi.encodeWithSelector(
IAuctionRebalanceModuleV1.startRebalance.selector,
_setToken,
_quoteAsset,
_newComponents,
_newComponentsAuctionParams,
_oldComponentsAuctionParams,
_shouldLockSetToken,
_rebalanceDuration,
_positionMultiplier
);
_invokeManager(_manager((_setToken)), address(auctionModule), callData);
}
// Constructs the claim that will be asserted at the Optimistic Oracle V3.
function _constructClaim(bytes32 proposalHash, bytes32 rulesHash) internal pure returns (bytes memory) {
return
abi.encodePacked(
AncillaryData.appendKeyValueBytes32("", PROPOSAL_HASH_KEY, proposalHash),
",",
RULES_KEY,
":\"",
rulesHash,
"\""
);
}
/**
* @notice Callback function that is called by Optimistic Oracle V3 when an assertion is resolved.
* @dev This function does nothing and is only here to satisfy the callback recipient interface.
* @param assertionId The identifier of the assertion that was resolved.
* @param assertedTruthfully Whether the assertion was resolved as truthful or not.
*/
function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) external {}
/**
* @notice Callback to automatically delete a proposal that was disputed.
* @param _assertionId the identifier of the disputed assertion.
*/
function assertionDisputedCallback(bytes32 _assertionId) external {
Proposal memory proposal = proposedProduct[_assertionId];
ProductSettings memory settings = productSettings[proposal.product];
require(address(settings.optimisticParams.optimisticOracleV3) != address(0), "Invalid oracle address");
// If the sender is the Optimistic Oracle V3, delete the proposal and associated assertionId.
if (msg.sender == address(settings.optimisticParams.optimisticOracleV3)) {
// Delete the disputed proposal and associated assertionId.
_deleteProposal(_assertionId);
} else {
// If the sender is not the expected Optimistic Oracle V3, check if the expected Oracle has the assertion and if not delete.
require(proposal.proposalHash != bytes32(0), "Invalid proposal hash");
require(settings.optimisticParams.optimisticOracleV3.getAssertion(_assertionId).asserter == address(0), "Oracle has assertion");
_deleteProposal(_assertionId);
}
emit ProposalDeleted(_assertionId, proposal);
}
/// @notice Pulls the higher of the minimum bond or configured bond amount from the sender.
/// @dev Internal function to pull the user's bond before asserting a claim.
/// @param optimisticRebalanceParams optimistic rebalance parameters for the product.
/// @return Bond amount pulled from the sender.
function _pullBond(OptimisticRebalanceParams memory optimisticRebalanceParams) internal returns (uint256) {
uint256 minimumBond = optimisticRebalanceParams.optimisticOracleV3.getMinimumBond(address(optimisticRebalanceParams.collateral));
uint256 totalBond = minimumBond > optimisticRebalanceParams.bondAmount ? minimumBond : optimisticRebalanceParams.bondAmount;
optimisticRebalanceParams.collateral.safeTransferFrom(msg.sender, address(this), totalBond);
optimisticRebalanceParams.collateral.safeIncreaseAllowance(address(optimisticRebalanceParams.optimisticOracleV3), totalBond);
return totalBond;
}
/// @notice Delete an existing proposal and associated assertionId.
/// @dev Internal function that deletes a proposal and associated assertionId.
/// @param assertionId assertionId of the proposal to delete.
function _deleteProposal(bytes32 assertionId) internal {
Proposal memory proposal = proposedProduct[assertionId];
delete assertionIds[proposal.proposalHash];
delete proposedProduct[assertionId];
}
}
GlobalStreamingFeeSplitExtension.sol 230 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
/**
* @title GlobalStreamingFeeSplitExtension
* @author Set Protocol
*
* Smart contract global extension which provides DelegatedManager owner and methodologist the ability to accrue and split
* streaming fees. Owner may configure the fee split percentages.
*
* Notes
* - the fee split is set on the Delegated Manager contract
* - when fees distributed via this contract will be inclusive of all fee types
*/
contract GlobalStreamingFeeSplitExtension is BaseGlobalExtension {
using Address for address;
using PreciseUnitMath for uint256;
using SafeMath for uint256;
/* ============ Events ============ */
event StreamingFeeSplitExtensionInitialized(
address indexed _setToken,
address indexed _delegatedManager
);
event FeesDistributed(
address _setToken,
address indexed _ownerFeeRecipient,
address indexed _methodologist,
uint256 _ownerTake,
uint256 _methodologistTake
);
/* ============ State Variables ============ */
// Instance of StreamingFeeModule
IStreamingFeeModule public immutable streamingFeeModule;
/* ============ Constructor ============ */
constructor(
IManagerCore _managerCore,
IStreamingFeeModule _streamingFeeModule
)
public
BaseGlobalExtension(_managerCore)
{
streamingFeeModule = _streamingFeeModule;
}
/* ============ External Functions ============ */
/**
* ANYONE CALLABLE: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for
* owner and methodologist, and sends to owner fee recipient and methodologist respectively.
*/
function accrueFeesAndDistribute(ISetToken _setToken) public {
// Emits a FeeActualized event
streamingFeeModule.accrueFee(_setToken);
IDelegatedManager delegatedManager = _manager(_setToken);
uint256 totalFees = _setToken.balanceOf(address(delegatedManager));
address methodologist = delegatedManager.methodologist();
address ownerFeeRecipient = delegatedManager.ownerFeeRecipient();
uint256 ownerTake = totalFees.preciseMul(delegatedManager.ownerFeeSplit());
uint256 methodologistTake = totalFees.sub(ownerTake);
if (ownerTake > 0) {
delegatedManager.transferTokens(address(_setToken), ownerFeeRecipient, ownerTake);
}
if (methodologistTake > 0) {
delegatedManager.transferTokens(address(_setToken), methodologist, methodologistTake);
}
emit FeesDistributed(address(_setToken), ownerFeeRecipient, methodologist, ownerTake, methodologistTake);
}
/**
* ONLY OWNER: Initializes StreamingFeeModule on the SetToken associated with the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize the StreamingFeeModule for
* @param _settings FeeState struct defining fee parameters for StreamingFeeModule initialization
*/
function initializeModule(
IDelegatedManager _delegatedManager,
IStreamingFeeModule.FeeState memory _settings
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
require(_delegatedManager.isInitializedExtension(address(this)), "Extension must be initialized");
_initializeModule(_delegatedManager.setToken(), _delegatedManager, _settings);
}
/**
* ONLY OWNER: Initializes StreamingFeeSplitExtension to the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
emit StreamingFeeSplitExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY OWNER: Initializes StreamingFeeSplitExtension to the DelegatedManager and StreamingFeeModule to the SetToken
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
* @param _settings FeeState struct defining fee parameters for StreamingFeeModule initialization
*/
function initializeModuleAndExtension(
IDelegatedManager _delegatedManager,
IStreamingFeeModule.FeeState memory _settings
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
_initializeModule(setToken, _delegatedManager, _settings);
emit StreamingFeeSplitExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the StreamingFeeSplitExtension
*/
function removeExtension() external override {
IDelegatedManager delegatedManager = IDelegatedManager(msg.sender);
ISetToken setToken = delegatedManager.setToken();
_removeExtension(setToken, delegatedManager);
}
/**
* ONLY OWNER: Updates streaming fee on StreamingFeeModule.
*
* NOTE: This will accrue streaming fees though not send to owner fee recipient and methodologist.
*
* @param _setToken Instance of the SetToken to update streaming fee for
* @param _newFee Percent of Set accruing to fee extension annually (1% = 1e16, 100% = 1e18)
*/
function updateStreamingFee(ISetToken _setToken, uint256 _newFee)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSignature("updateStreamingFee(address,uint256)", _setToken, _newFee);
_invokeManager(_manager(_setToken), address(streamingFeeModule), callData);
}
/**
* ONLY OWNER: Updates fee recipient on StreamingFeeModule
*
* @param _setToken Instance of the SetToken to update fee recipient for
* @param _newFeeRecipient Address of new fee recipient. This should be the address of the DelegatedManager
*/
function updateFeeRecipient(ISetToken _setToken, address _newFeeRecipient)
external
onlyOwner(_setToken)
{
bytes memory callData = abi.encodeWithSignature("updateFeeRecipient(address,address)", _setToken, _newFeeRecipient);
_invokeManager(_manager(_setToken), address(streamingFeeModule), callData);
}
/* ============ Internal Functions ============ */
/**
* Internal function to initialize StreamingFeeModule on the SetToken associated with the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
* @param _settings FeeState struct defining fee parameters for StreamingFeeModule initialization
*/
function _initializeModule(
ISetToken _setToken,
IDelegatedManager _delegatedManager,
IStreamingFeeModule.FeeState memory _settings
)
internal
{
bytes memory callData = abi.encodeWithSignature(
"initialize(address,(address,uint256,uint256,uint256))",
_setToken,
_settings);
_invokeManager(_delegatedManager, address(streamingFeeModule), callData);
}
}
GlobalTradeExtension.sol 166 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ITradeModule } from "../interfaces/ITradeModule.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
/**
* @title GlobalTradeExtension
* @author Set Protocol
*
* Smart contract global extension which provides DelegatedManager privileged operator(s) the ability to trade on a DEX
* and the owner the ability to restrict operator(s) permissions with an asset whitelist.
*/
contract GlobalTradeExtension is BaseGlobalExtension {
/* ============ Events ============ */
event TradeExtensionInitialized(
address indexed _setToken,
address indexed _delegatedManager
);
/* ============ State Variables ============ */
// Instance of TradeModule
ITradeModule public immutable tradeModule;
/* ============ Constructor ============ */
constructor(
IManagerCore _managerCore,
ITradeModule _tradeModule
)
public
BaseGlobalExtension(_managerCore)
{
tradeModule = _tradeModule;
}
/* ============ External Functions ============ */
/**
* ONLY OWNER: Initializes TradeModule on the SetToken associated with the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
*/
function initializeModule(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
require(_delegatedManager.isInitializedExtension(address(this)), "Extension must be initialized");
_initializeModule(_delegatedManager.setToken(), _delegatedManager);
}
/**
* ONLY OWNER: Initializes TradeExtension to the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
emit TradeExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY OWNER: Initializes TradeExtension to the DelegatedManager and TradeModule to the SetToken
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeModuleAndExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager){
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
_initializeModule(setToken, _delegatedManager);
emit TradeExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the TradeExtension
*/
function removeExtension() external override {
IDelegatedManager delegatedManager = IDelegatedManager(msg.sender);
ISetToken setToken = delegatedManager.setToken();
_removeExtension(setToken, delegatedManager);
}
/**
* ONLY OPERATOR: Executes a trade on a supported DEX.
* @dev Although the SetToken units are passed in for the send and receive quantities, the total quantity
* sent and received is the quantity of SetToken units multiplied by the SetToken totalSupply.
*
* @param _setToken Instance of the SetToken to trade
* @param _exchangeName Human readable name of the exchange in the integrations registry
* @param _sendToken Address of the token to be sent to the exchange
* @param _sendQuantity Units of token in SetToken sent to the exchange
* @param _receiveToken Address of the token that will be received from the exchange
* @param _minReceiveQuantity Min units of token in SetToken to be received from the exchange
* @param _data Arbitrary bytes to be used to construct trade call data
*/
function trade(
ISetToken _setToken,
string memory _exchangeName,
address _sendToken,
uint256 _sendQuantity,
address _receiveToken,
uint256 _minReceiveQuantity,
bytes memory _data
)
external
onlyOperator(_setToken)
onlyAllowedAsset(_setToken, _receiveToken)
{
bytes memory callData = abi.encodeWithSignature(
"trade(address,string,address,uint256,address,uint256,bytes)",
_setToken,
_exchangeName,
_sendToken,
_sendQuantity,
_receiveToken,
_minReceiveQuantity,
_data
);
_invokeManager(_manager(_setToken), address(tradeModule), callData);
}
/* ============ Internal Functions ============ */
/**
* Internal function to initialize TradeModule on the SetToken associated with the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize the TradeModule for
*/
function _initializeModule(ISetToken _setToken, IDelegatedManager _delegatedManager) internal {
bytes memory callData = abi.encodeWithSignature("initialize(address)", _setToken);
_invokeManager(_delegatedManager, address(tradeModule), callData);
}
}
GlobalWrapExtension.sol 264 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IWETH } from "../interfaces/IWETH.sol";
import { IWrapModuleV2 } from "../interfaces/IWrapModuleV2.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
/**
* @title GlobalWrapExtension
* @author Set Protocol
*
* Smart contract global extension which provides DelegatedManager operator(s) the ability to wrap ERC20 and Ether positions
* via third party protocols.
*
* Some examples of wrap actions include wrapping, DAI to cDAI (Compound) or Dai to aDai (AAVE).
*/
contract GlobalWrapExtension is BaseGlobalExtension {
/* ============ Events ============ */
event WrapExtensionInitialized(
address indexed _setToken,
address indexed _delegatedManager
);
/* ============ State Variables ============ */
// Instance of WrapModuleV2
IWrapModuleV2 public immutable wrapModule;
/* ============ Constructor ============ */
/**
* Instantiate with ManagerCore address and WrapModuleV2 address.
*
* @param _managerCore Address of ManagerCore contract
* @param _wrapModule Address of WrapModuleV2 contract
*/
constructor(
IManagerCore _managerCore,
IWrapModuleV2 _wrapModule
)
public
BaseGlobalExtension(_managerCore)
{
wrapModule = _wrapModule;
}
/* ============ External Functions ============ */
/**
* ONLY OWNER: Initializes WrapModuleV2 on the SetToken associated with the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize the WrapModuleV2 for
*/
function initializeModule(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
_initializeModule(_delegatedManager.setToken(), _delegatedManager);
}
/**
* ONLY OWNER: Initializes WrapExtension to the DelegatedManager.
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager) {
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
emit WrapExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY OWNER: Initializes WrapExtension to the DelegatedManager and TradeModule to the SetToken
*
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function initializeModuleAndExtension(IDelegatedManager _delegatedManager) external onlyOwnerAndValidManager(_delegatedManager){
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
_initializeModule(setToken, _delegatedManager);
emit WrapExtensionInitialized(address(setToken), address(_delegatedManager));
}
/**
* ONLY MANAGER: Remove an existing SetToken and DelegatedManager tracked by the WrapExtension
*/
function removeExtension() external override {
IDelegatedManager delegatedManager = IDelegatedManager(msg.sender);
ISetToken setToken = delegatedManager.setToken();
_removeExtension(setToken, delegatedManager);
}
/**
* ONLY OPERATOR: Instructs the SetToken to wrap an underlying asset into a wrappedToken via a specified adapter.
*
* @param _setToken Instance of the SetToken
* @param _underlyingToken Address of the component to be wrapped
* @param _wrappedToken Address of the desired wrapped token
* @param _underlyingUnits Quantity of underlying units in Position units
* @param _integrationName Name of wrap module integration (mapping on integration registry)
* @param _wrapData Arbitrary bytes to pass into the WrapV2Adapter
*/
function wrap(
ISetToken _setToken,
address _underlyingToken,
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName,
bytes memory _wrapData
)
external
onlyOperator(_setToken)
onlyAllowedAsset(_setToken, _wrappedToken)
{
bytes memory callData = abi.encodeWithSelector(
IWrapModuleV2.wrap.selector,
_setToken,
_underlyingToken,
_wrappedToken,
_underlyingUnits,
_integrationName,
_wrapData
);
_invokeManager(_manager(_setToken), address(wrapModule), callData);
}
/**
* ONLY OPERATOR: Instructs the SetToken to wrap Ether into a wrappedToken via a specified adapter. Since SetTokens
* only hold WETH, in order to support protocols that collateralize with Ether the SetToken's WETH must be unwrapped
* first before sending to the external protocol.
*
* @param _setToken Instance of the SetToken
* @param _wrappedToken Address of the desired wrapped token
* @param _underlyingUnits Quantity of underlying units in Position units
* @param _integrationName Name of wrap module integration (mapping on integration registry)
* @param _wrapData Arbitrary bytes to pass into the WrapV2Adapter
*/
function wrapWithEther(
ISetToken _setToken,
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName,
bytes memory _wrapData
)
external
onlyOperator(_setToken)
onlyAllowedAsset(_setToken, _wrappedToken)
{
bytes memory callData = abi.encodeWithSelector(
IWrapModuleV2.wrapWithEther.selector,
_setToken,
_wrappedToken,
_underlyingUnits,
_integrationName,
_wrapData
);
_invokeManager(_manager(_setToken), address(wrapModule), callData);
}
/**
* ONLY OPERATOR: Instructs the SetToken to unwrap a wrapped asset into its underlying via a specified adapter.
*
* @param _setToken Instance of the SetToken
* @param _underlyingToken Address of the underlying asset
* @param _wrappedToken Address of the component to be unwrapped
* @param _wrappedUnits Quantity of wrapped tokens in Position units
* @param _integrationName ID of wrap module integration (mapping on integration registry)
* @param _unwrapData Arbitrary bytes to pass into the WrapV2Adapter
*/
function unwrap(
ISetToken _setToken,
address _underlyingToken,
address _wrappedToken,
uint256 _wrappedUnits,
string calldata _integrationName,
bytes memory _unwrapData
)
external
onlyOperator(_setToken)
onlyAllowedAsset(_setToken, _underlyingToken)
{
bytes memory callData = abi.encodeWithSelector(
IWrapModuleV2.unwrap.selector,
_setToken,
_underlyingToken,
_wrappedToken,
_wrappedUnits,
_integrationName,
_unwrapData
);
_invokeManager(_manager(_setToken), address(wrapModule), callData);
}
/**
* ONLY OPERATOR: Instructs the SetToken to unwrap a wrapped asset collateralized by Ether into Wrapped Ether. Since
* external protocol will send back Ether that Ether must be Wrapped into WETH in order to be accounted for by SetToken.
*
* @param _setToken Instance of the SetToken
* @param _wrappedToken Address of the component to be unwrapped
* @param _wrappedUnits Quantity of wrapped tokens in Position units
* @param _integrationName ID of wrap module integration (mapping on integration registry)
* @param _unwrapData Arbitrary bytes to pass into the WrapV2Adapter
*/
function unwrapWithEther(
ISetToken _setToken,
address _wrappedToken,
uint256 _wrappedUnits,
string calldata _integrationName,
bytes memory _unwrapData
)
external
onlyOperator(_setToken)
onlyAllowedAsset(_setToken, address(wrapModule.weth()))
{
bytes memory callData = abi.encodeWithSelector(
IWrapModuleV2.unwrapWithEther.selector,
_setToken,
_wrappedToken,
_wrappedUnits,
_integrationName,
_unwrapData
);
_invokeManager(_manager(_setToken), address(wrapModule), callData);
}
/* ============ Internal Functions ============ */
/**
* Internal function to initialize WrapModuleV2 on the SetToken associated with the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize the WrapModuleV2 for
*/
function _initializeModule(ISetToken _setToken, IDelegatedManager _delegatedManager) internal {
bytes memory callData = abi.encodeWithSelector(IWrapModuleV2.initialize.selector, _setToken);
_invokeManager(_delegatedManager, address(wrapModule), callData);
}
}
AirdropIssuanceHook.sol 80 lines
/*
Copyright 2021 Index Cooperative.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.6.10;
import { IAirdropModule } from "../interfaces/IAirdropModule.sol";
import { IManagerIssuanceHook } from "../interfaces/IManagerIssuanceHook.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title AirdropIssuanceHook
* @author Index Coop
*
* Issuance hooks that absorbs all airdropped tokens. Useful for ensuring that rebasing tokens are fully accounted for before issuance. Only works
* with tokens that strictly positively rebase such as aTokens.
*/
contract AirdropIssuanceHook is IManagerIssuanceHook {
/* ============ State Variables ============ */
// Address of Set Protocol AirdropModule
IAirdropModule public airdropModule;
/* ============== Constructor ================ */
/**
* Sets state variables.
*
* @param _airdropModule address of AirdropModule
*/
constructor(IAirdropModule _airdropModule) public {
airdropModule = _airdropModule;
}
/* =========== External Functions =========== */
/**
* Absorbs all airdropped tokens. Called by some issuance modules before issuance.
*
* @param _setToken address of SetToken to absorb airdrops for
*/
function invokePreIssueHook(ISetToken _setToken, uint256 /* _issueQuantity */, address /* _sender */, address /* _to */) external override {
_sync(_setToken);
}
/**
* Absorbs all airdropped tokens. Called by some issuance modules before redemption.
*
* @param _setToken address of SetToken to absorb airdrops for
*/
function invokePreRedeemHook(ISetToken _setToken, uint256 /* _issueQuantity */, address /* _sender */, address /* _to */) external override {
_sync(_setToken);
}
/* =========== Internal Functions ========== */
/**
* Absorbs all airdropped tokens. AirdropModule must be added to an initialized for the SetToken. Must have anyoneAbsorb set to true on
* the AirdropModule.
*
* @param _setToken address of SetToken to absorb airdrops for
*/
function _sync(ISetToken _setToken) internal {
address[] memory airdrops = airdropModule.getAirdrops(_setToken);
airdropModule.batchAbsorb(_setToken, airdrops);
}
}
SupplyCapAllowedCallerIssuanceHook.sol 159 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { IManagerIssuanceHook } from "../interfaces/IManagerIssuanceHook.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title SupplyCapAllowedCallerIssuanceHook
* @author Set Protocol
*
* Issuance hook that checks
* 1) New issuances won't push SetToken totalSupply over supply cap
* 2) A contract address is allowed to call the module. This does not apply if caller is an EOA
*/
contract SupplyCapAllowedCallerIssuanceHook is Ownable, IManagerIssuanceHook {
using SafeMath for uint256;
using AddressArrayUtils for address[];
/* ============ Events ============ */
event SupplyCapUpdated(uint256 _newCap);
event CallerStatusUpdated(address indexed _caller, bool _status);
event AnyoneCallableUpdated(bool indexed _status);
/* ============ State Variables ============ */
// Cap on totalSupply of Sets
uint256 public supplyCap;
// Boolean indicating if anyone can call function
bool public anyoneCallable;
// Mapping of contract addresses allowed to call function
mapping(address => bool) public callAllowList;
/* ============ Constructor ============ */
/**
* Constructor, overwrites owner and original supply cap.
*
* @param _initialOwner Owner address, overwrites Ownable logic which sets to deployer as default
* @param _supplyCap Supply cap for Set (in wei of Set)
*/
constructor(
address _initialOwner,
uint256 _supplyCap
)
public
{
supplyCap = _supplyCap;
// Overwrite _owner param of Ownable contract
transferOwnership(_initialOwner);
}
/* ============ External Functions ============ */
/**
* Adheres to IManagerIssuanceHook interface, and checks to make sure the current issue call won't push total supply over cap.
*/
function invokePreIssueHook(
ISetToken _setToken,
uint256 _issueQuantity,
address _sender,
address /*_to*/
)
external
override
{
_validateAllowedContractCaller(_sender);
uint256 totalSupply = _setToken.totalSupply();
require(totalSupply.add(_issueQuantity) <= supplyCap, "Supply cap exceeded");
}
/**
* Adheres to IManagerIssuanceHook interface
*/
function invokePreRedeemHook(
ISetToken _setToken,
uint256 _redeemQuantity,
address _sender,
address _to
)
external
override
{}
/**
* ONLY OWNER: Updates supply cap
*/
function updateSupplyCap(uint256 _newCap) external onlyOwner {
supplyCap = _newCap;
SupplyCapUpdated(_newCap);
}
/**
* ONLY OWNER: Toggle ability for passed addresses to call only allowed caller functions
*
* @param _callers Array of caller addresses to toggle status
* @param _statuses Array of statuses for each caller
*/
function updateCallerStatus(address[] calldata _callers, bool[] calldata _statuses) external onlyOwner {
_callers.validatePairsWithArray(_statuses);
for (uint256 i = 0; i < _callers.length; i++) {
address caller = _callers[i];
bool status = _statuses[i];
callAllowList[caller] = status;
emit CallerStatusUpdated(caller, status);
}
}
/**
* ONLY OWNER: Toggle whether anyone can call function, bypassing the callAllowlist
*
* @param _status Boolean indicating whether to allow anyone call
*/
function updateAnyoneCallable(bool _status) external onlyOwner {
anyoneCallable = _status;
emit AnyoneCallableUpdated(_status);
}
/* ============ Internal Functions ============ */
/**
* Validate if passed address is allowed to call function. If anyoneCallable is set to true, anyone can call otherwise needs to be an EOA or
* approved contract address.
*/
function _validateAllowedContractCaller(address _caller) internal view {
require(
_caller == tx.origin || anyoneCallable || callAllowList[_caller],
"Contract not permitted to call"
);
}
}
SupplyCapIssuanceHook.sol 104 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IManagerIssuanceHook } from "../interfaces/IManagerIssuanceHook.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title SupplyCapIssuanceHook
* @author Set Protocol
*
* Issuance hook that checks new issuances won't push SetToken totalSupply over supply cap.
*/
contract SupplyCapIssuanceHook is Ownable, IManagerIssuanceHook {
using SafeMath for uint256;
/* ============ Events ============ */
event SupplyCapUpdated(uint256 _newCap);
/* ============ State Variables ============ */
// Cap on totalSupply of Sets
uint256 public supplyCap;
/* ============ Constructor ============ */
/**
* Constructor, overwrites owner and original supply cap.
*
* @param _initialOwner Owner address, overwrites Ownable logic which sets to deployer as default
* @param _supplyCap Supply cap for Set (in wei of Set)
*/
constructor(
address _initialOwner,
uint256 _supplyCap
)
public
{
supplyCap = _supplyCap;
// Overwrite _owner param of Ownable contract
transferOwnership(_initialOwner);
}
/**
* Adheres to IManagerIssuanceHook interface, and checks to make sure the current issue call won't push total supply over cap.
*/
function invokePreIssueHook(
ISetToken _setToken,
uint256 _issueQuantity,
address /*_sender*/,
address /*_to*/
)
external
override
{
uint256 totalSupply = _setToken.totalSupply();
require(totalSupply.add(_issueQuantity) <= supplyCap, "Supply cap exceeded");
}
/**
* Adheres to IManagerIssuanceHook interface
*/
function invokePreRedeemHook(
ISetToken _setToken,
uint256 _redeemQuantity,
address _sender,
address _to
)
external
override
{}
/**
* ONLY OWNER: Updates supply cap
*/
function updateSupplyCap(uint256 _newCap) external onlyOwner {
supplyCap = _newCap;
SupplyCapUpdated(_newCap);
}
}
CErc20Storage.sol 24 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
contract CErc20Storage {
/**
* @notice Underlying asset for this CToken
*/
address public underlying;
}
CompoundLeverageModuleStorage.sol 22 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
contract CompoundLeverageModuleStorage {
// Mapping of underlying to CToken. If ETH, then map WETH to cETH
mapping(address => address) public underlyingToCToken;
}
Datatypes.sol 268 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.6.10;
library DataTypes {
struct ReserveData {
//stores the reserve configuration
ReserveConfigurationMap configuration;
//the liquidity index. Expressed in ray
uint128 liquidityIndex;
//the current supply rate. Expressed in ray
uint128 currentLiquidityRate;
//variable borrow index. Expressed in ray
uint128 variableBorrowIndex;
//the current variable borrow rate. Expressed in ray
uint128 currentVariableBorrowRate;
//the current stable borrow rate. Expressed in ray
uint128 currentStableBorrowRate;
//timestamp of last update
uint40 lastUpdateTimestamp;
//the id of the reserve. Represents the position in the list of the active reserves
uint16 id;
//aToken address
address aTokenAddress;
//stableDebtToken address
address stableDebtTokenAddress;
//variableDebtToken address
address variableDebtTokenAddress;
//address of the interest rate strategy
address interestRateStrategyAddress;
//the current treasury balance, scaled
uint128 accruedToTreasury;
//the outstanding unbacked aTokens minted through the bridging feature
uint128 unbacked;
//the outstanding debt borrowed against this asset in isolation mode
uint128 isolationModeTotalDebt;
}
struct ReserveConfigurationMap {
//bit 0-15: LTV
//bit 16-31: Liq. threshold
//bit 32-47: Liq. bonus
//bit 48-55: Decimals
//bit 56: reserve is active
//bit 57: reserve is frozen
//bit 58: borrowing is enabled
//bit 59: stable rate borrowing enabled
//bit 60: asset is paused
//bit 61: borrowing in isolation mode is enabled
//bit 62-63: reserved
//bit 64-79: reserve factor
//bit 80-115 borrow cap in whole tokens, borrowCap == 0 => no cap
//bit 116-151 supply cap in whole tokens, supplyCap == 0 => no cap
//bit 152-167 liquidation protocol fee
//bit 168-175 eMode category
//bit 176-211 unbacked mint cap in whole tokens, unbackedMintCap == 0 => minting disabled
//bit 212-251 debt ceiling for isolation mode with (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals
//bit 252-255 unused
uint256 data;
}
struct UserConfigurationMap {
/**
* @dev Bitmap of the users collaterals and borrows. It is divided in pairs of bits, one pair per asset.
* The first bit indicates if an asset is used as collateral by the user, the second whether an
* asset is borrowed by the user.
*/
uint256 data;
}
struct EModeCategory {
// each eMode category has a custom ltv and liquidation threshold
uint16 ltv;
uint16 liquidationThreshold;
uint16 liquidationBonus;
// each eMode category may or may not have a custom oracle to override the individual assets price oracles
address priceSource;
string label;
}
enum InterestRateMode {
NONE,
STABLE,
VARIABLE
}
struct ReserveCache {
uint256 currScaledVariableDebt;
uint256 nextScaledVariableDebt;
uint256 currPrincipalStableDebt;
uint256 currAvgStableBorrowRate;
uint256 currTotalStableDebt;
uint256 nextAvgStableBorrowRate;
uint256 nextTotalStableDebt;
uint256 currLiquidityIndex;
uint256 nextLiquidityIndex;
uint256 currVariableBorrowIndex;
uint256 nextVariableBorrowIndex;
uint256 currLiquidityRate;
uint256 currVariableBorrowRate;
uint256 reserveFactor;
ReserveConfigurationMap reserveConfiguration;
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
uint40 reserveLastUpdateTimestamp;
uint40 stableDebtLastUpdateTimestamp;
}
struct ExecuteLiquidationCallParams {
uint256 reservesCount;
uint256 debtToCover;
address collateralAsset;
address debtAsset;
address user;
bool receiveAToken;
address priceOracle;
uint8 userEModeCategory;
address priceOracleSentinel;
}
struct ExecuteSupplyParams {
address asset;
uint256 amount;
address onBehalfOf;
uint16 referralCode;
}
struct ExecuteBorrowParams {
address asset;
address user;
address onBehalfOf;
uint256 amount;
InterestRateMode interestRateMode;
uint16 referralCode;
bool releaseUnderlying;
uint256 maxStableRateBorrowSizePercent;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
address priceOracleSentinel;
}
struct ExecuteRepayParams {
address asset;
uint256 amount;
InterestRateMode interestRateMode;
address onBehalfOf;
bool useATokens;
}
struct ExecuteWithdrawParams {
address asset;
uint256 amount;
address to;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
}
struct ExecuteSetUserEModeParams {
uint256 reservesCount;
address oracle;
uint8 categoryId;
}
struct FinalizeTransferParams {
address asset;
address from;
address to;
uint256 amount;
uint256 balanceFromBefore;
uint256 balanceToBefore;
uint256 reservesCount;
address oracle;
uint8 fromEModeCategory;
}
struct FlashloanParams {
address receiverAddress;
address[] assets;
uint256[] amounts;
uint256[] interestRateModes;
address onBehalfOf;
bytes params;
uint16 referralCode;
uint256 flashLoanPremiumToProtocol;
uint256 flashLoanPremiumTotal;
uint256 maxStableRateBorrowSizePercent;
uint256 reservesCount;
address addressesProvider;
uint8 userEModeCategory;
bool isAuthorizedFlashBorrower;
}
struct FlashloanSimpleParams {
address receiverAddress;
address asset;
uint256 amount;
bytes params;
uint16 referralCode;
uint256 flashLoanPremiumToProtocol;
uint256 flashLoanPremiumTotal;
}
struct FlashLoanRepaymentParams {
uint256 amount;
uint256 totalPremium;
uint256 flashLoanPremiumToProtocol;
address asset;
address receiverAddress;
uint16 referralCode;
}
struct CalculateUserAccountDataParams {
UserConfigurationMap userConfig;
uint256 reservesCount;
address user;
address oracle;
uint8 userEModeCategory;
}
struct ValidateBorrowParams {
ReserveCache reserveCache;
UserConfigurationMap userConfig;
address asset;
address userAddress;
uint256 amount;
InterestRateMode interestRateMode;
uint256 maxStableLoanPercent;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
address priceOracleSentinel;
bool isolationModeActive;
address isolationModeCollateralAddress;
uint256 isolationModeDebtCeiling;
}
struct ValidateLiquidationCallParams {
ReserveCache debtReserveCache;
uint256 totalDebt;
uint256 healthFactor;
address priceOracleSentinel;
}
struct CalculateInterestRatesParams {
uint256 unbacked;
uint256 liquidityAdded;
uint256 liquidityTaken;
uint256 totalStableDebt;
uint256 totalVariableDebt;
uint256 averageStableBorrowRate;
uint256 reserveFactor;
address reserve;
address aToken;
}
struct InitReserveParams {
address asset;
address aTokenAddress;
address stableDebtAddress;
address variableDebtAddress;
address interestRateStrategyAddress;
uint16 reservesCount;
uint16 maxNumberReserves;
}
}
IVault.sol 273 lines
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity >=0.6.10 <0.9.0;
pragma experimental ABIEncoderV2;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IFlashLoanRecipient {
/**
* @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
*
* At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
* call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
* Vault, or else the entire flash loan will revert.
*
* `userData` is the same value passed in the `IVault.flashLoan` call.
*/
function receiveFlashLoan(
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
) external;
}
/**
* Stripped down interface of IVault.
* https://github.com/balancer/balancer-v2-monorepo/blob/master/pkg/interfaces/contracts/vault/IVault.sol
*/
interface IVault {
// Swaps
//
// Users can swap tokens with Pools by calling the `swap` and `batchSwap` functions. To do this,
// they need not trust Pool contracts in any way: all security checks are made by the Vault. They must however be
// aware of the Pools' pricing algorithms in order to estimate the prices Pools will quote.
//
// The `swap` function executes a single swap, while `batchSwap` can perform multiple swaps in sequence.
// In each individual swap, tokens of one kind are sent from the sender to the Pool (this is the 'token in'),
// and tokens of another kind are sent from the Pool to the recipient in exchange (this is the 'token out').
// More complex swaps, such as one token in to multiple tokens out can be achieved by batching together
// individual swaps.
//
// There are two swap kinds:
// - 'given in' swaps, where the amount of tokens in (sent to the Pool) is known, and the Pool determines (via the
// `onSwap` hook) the amount of tokens out (to send to the recipient).
// - 'given out' swaps, where the amount of tokens out (received from the Pool) is known, and the Pool determines
// (via the `onSwap` hook) the amount of tokens in (to receive from the sender).
//
// Additionally, it is possible to chain swaps using a placeholder input amount, which the Vault replaces with
// the calculated output of the previous swap. If the previous swap was 'given in', this will be the calculated
// tokenOut amount. If the previous swap was 'given out', it will use the calculated tokenIn amount. These extended
// swaps are known as 'multihop' swaps, since they 'hop' through a number of intermediate tokens before arriving at
// the final intended token.
//
// In all cases, tokens are only transferred in and out of the Vault (or withdrawn from and deposited into Internal
// Balance) after all individual swaps have been completed, and the net token balance change computed. This makes
// certain swap patterns, such as multihops, or swaps that interact with the same token pair in multiple Pools, cost
// much less gas than they would otherwise.
//
// It also means that under certain conditions it is possible to perform arbitrage by swapping with multiple
// Pools in a way that results in net token movement out of the Vault (profit), with no tokens being sent in (only
// updating the Pool's internal accounting).
//
// To protect users from front-running or the market changing rapidly, they supply a list of 'limits' for each token
// involved in the swap, where either the maximum number of tokens to send (by passing a positive value) or the
// minimum amount of tokens to receive (by passing a negative value) is specified.
//
// Additionally, a 'deadline' timestamp can also be provided, forcing the swap to fail if it occurs after
// this point in time (e.g. if the transaction failed to be included in a block promptly).
//
// If interacting with Pools that hold WETH, it is possible to both send and receive ETH directly: the Vault will do
// the wrapping and unwrapping. To enable this mechanism, the IAsset sentinel value (the zero address) must be
// passed in the `assets` array instead of the WETH address. Note that it is possible to combine ETH and WETH in the
// same swap. Any excess ETH will be sent back to the caller (not the sender, which is relevant for relayers).
//
// Finally, Internal Balance can be used when either sending or receiving tokens.
enum SwapKind {
GIVEN_IN,
GIVEN_OUT
}
/**
* @dev Performs a swap with a single Pool.
*
* If the swap is 'given in' (the number of tokens to send to the Pool is known), it returns the amount of tokens
* taken from the Pool, which must be greater than or equal to `limit`.
*
* If the swap is 'given out' (the number of tokens to take from the Pool is known), it returns the amount of tokens
* sent to the Pool, which must be less than or equal to `limit`.
*
* Internal Balance usage and the recipient are determined by the `funds` struct.
*
* Emits a `Swap` event.
*/
function swap(
SingleSwap memory singleSwap,
FundManagement memory funds,
uint256 limit,
uint256 deadline
) external payable returns (uint256);
/**
* @dev Data for a single swap executed by `swap`. `amount` is either `amountIn` or `amountOut` depending on
* the `kind` value.
*
* `assetIn` and `assetOut` are either token addresses, or the IAsset sentinel value for ETH (the zero address).
* Note that Pools never interact with ETH directly: it will be wrapped to or unwrapped from WETH by the Vault.
*
* The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
* used to extend swap behavior.
*/
struct SingleSwap {
bytes32 poolId;
SwapKind kind;
address assetIn;
address assetOut;
uint256 amount;
bytes userData;
}
/**
* @dev Performs a series of swaps with one or multiple Pools. In each individual swap, the caller determines either
* the amount of tokens sent to or received from the Pool, depending on the `kind` value.
*
* Returns an array with the net Vault asset balance deltas. Positive amounts represent tokens (or ETH) sent to the
* Vault, and negative amounts represent tokens (or ETH) sent by the Vault. Each delta corresponds to the asset at
* the same index in the `assets` array.
*
* Swaps are executed sequentially, in the order specified by the `swaps` array. Each array element describes a
* Pool, the token to be sent to this Pool, the token to receive from it, and an amount that is either `amountIn` or
* `amountOut` depending on the swap kind.
*
* Multihop swaps can be executed by passing an `amount` value of zero for a swap. This will cause the amount in/out
* of the previous swap to be used as the amount in for the current one. In a 'given in' swap, 'tokenIn' must equal
* the previous swap's `tokenOut`. For a 'given out' swap, `tokenOut` must equal the previous swap's `tokenIn`.
*
* The `assets` array contains the addresses of all assets involved in the swaps. These are either token addresses,
* or the IAsset sentinel value for ETH (the zero address). Each entry in the `swaps` array specifies tokens in and
* out by referencing an index in `assets`. Note that Pools never interact with ETH directly: it will be wrapped to
* or unwrapped from WETH by the Vault.
*
* Internal Balance usage, sender, and recipient are determined by the `funds` struct. The `limits` array specifies
* the minimum or maximum amount of each token the vault is allowed to transfer.
*
* `batchSwap` can be used to make a single swap, like `swap` does, but doing so requires more gas than the
* equivalent `swap` call.
*
* Emits `Swap` events.
*/
function batchSwap(
SwapKind kind,
BatchSwapStep[] memory swaps,
address[] memory assets,
FundManagement memory funds,
int256[] memory limits,
uint256 deadline
) external payable returns (int256[] memory);
/**
* @dev Data for each individual swap executed by `batchSwap`. The asset in and out fields are indexes into the
* `assets` array passed to that function, and ETH assets are converted to WETH.
*
* If `amount` is zero, the multihop mechanism is used to determine the actual amount based on the amount in/out
* from the previous swap, depending on the swap kind.
*
* The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
* used to extend swap behavior.
*/
struct BatchSwapStep {
bytes32 poolId;
uint256 assetInIndex;
uint256 assetOutIndex;
uint256 amount;
bytes userData;
}
/**
* @dev Emitted for each individual swap performed by `swap` or `batchSwap`.
*/
event Swap(
bytes32 indexed poolId,
IERC20 indexed tokenIn,
IERC20 indexed tokenOut,
uint256 amountIn,
uint256 amountOut
);
/**
* @dev All tokens in a swap are either sent from the `sender` account to the Vault, or from the Vault to the
* `recipient` account.
*
* If the caller is not `sender`, it must be an authorized relayer for them.
*
* If `fromInternalBalance` is true, the `sender`'s Internal Balance will be preferred, performing an ERC20
* transfer for the difference between the requested amount and the User's Internal Balance (if any). The `sender`
* must have allowed the Vault to use their tokens via `IERC20.approve()`. This matches the behavior of
* `joinPool`.
*
* If `toInternalBalance` is true, tokens will be deposited to `recipient`'s internal balance instead of
* transferred. This matches the behavior of `exitPool`.
*
* Note that ETH cannot be deposited to or withdrawn from Internal Balance: attempting to do so will trigger a
* revert.
*/
struct FundManagement {
address sender;
bool fromInternalBalance;
address payable recipient;
bool toInternalBalance;
}
/**
* @dev Simulates a call to `batchSwap`, returning an array of Vault asset deltas. Calls to `swap` cannot be
* simulated directly, but an equivalent `batchSwap` call can and will yield the exact same result.
*
* Each element in the array corresponds to the asset at the same index, and indicates the number of tokens (or ETH)
* the Vault would take from the sender (if positive) or send to the recipient (if negative). The arguments it
* receives are the same that an equivalent `batchSwap` call would receive.
*
* Unlike `batchSwap`, this function performs no checks on the sender or recipient field in the `funds` struct.
* This makes it suitable to be called by off-chain applications via eth_call without needing to hold tokens,
* approve them for the Vault, or even know a user's address.
*
* Note that this function is not 'view' (due to implementation details): the client code must explicitly execute
* eth_call instead of eth_sendTransaction.
*/
function queryBatchSwap(
SwapKind kind,
BatchSwapStep[] memory swaps,
address[] memory assets,
FundManagement memory funds
) external returns (int256[] memory assetDeltas);
// Flash Loans
/**
* @dev Performs a 'flash loan', sending tokens to `recipient`, executing the `receiveFlashLoan` hook on it,
* and then reverting unless the tokens plus a proportional protocol fee have been returned.
*
* The `tokens` and `amounts` arrays must have the same length, and each entry in these indicates the loan amount
* for each token contract. `tokens` must be sorted in ascending order.
*
* The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the
* `receiveFlashLoan` call.
*
* Emits `FlashLoan` events.
*/
function flashLoan(
IFlashLoanRecipient recipient,
address[] memory tokens,
uint256[] memory amounts,
bytes memory userData
) external;
/**
* @dev Emitted for each individual flash loan performed by `flashLoan`.
*/
event FlashLoan(IFlashLoanRecipient indexed recipient, IERC20 indexed token, uint256 amount, uint256 feeAmount);
}
IAcrossHubPoolV2.sol 97 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface IAcrossHubPoolV2 {
function addLiquidity(address l1Token, uint256 l1TokenAmount) external payable;
function bondAmount() external view returns (uint256);
function bondToken() external view returns (address);
function claimProtocolFeesCaptured(address l1Token) external;
function crossChainContracts(uint256) external view returns (address adapter, address spokePool);
function disableL1TokenForLiquidityProvision(address l1Token) external;
function disputeRootBundle() external;
function emergencyDeleteProposal() external;
function enableL1TokenForLiquidityProvision(address l1Token) external;
function exchangeRateCurrent(address l1Token) external returns (uint256);
function executeRootBundle(
uint256 chainId,
uint256 groupIndex,
uint256[] memory bundleLpFees,
int256[] memory netSendAmounts,
int256[] memory runningBalances,
uint8 leafId,
address[] memory l1Tokens,
bytes32[] memory proof
) external;
function finder() external view returns (address);
function getCurrentTime() external view returns (uint256);
function haircutReserves(address l1Token, int256 haircutAmount) external;
function identifier() external view returns (bytes32);
function liquidityUtilizationCurrent(address l1Token) external returns (uint256);
function liquidityUtilizationPostRelay(address l1Token, uint256 relayedAmount) external returns (uint256);
function liveness() external view returns (uint32);
function loadEthForL2Calls() external payable;
function lpFeeRatePerSecond() external view returns (uint256);
function lpTokenFactory() external view returns (address);
function multicall(bytes[] memory data) external payable returns (bytes[] memory results);
function owner() external view returns (address);
function paused() external view returns (bool);
function poolRebalanceRoute(uint256 destinationChainId, address l1Token)
external
view
returns (address destinationToken);
function pooledTokens(address)
external
view
returns (
address lpToken,
bool isEnabled,
uint32 lastLpFeeUpdate,
int256 utilizedReserves,
uint256 liquidReserves,
uint256 undistributedLpFees
);
function proposeRootBundle(
uint256[] memory bundleEvaluationBlockNumbers,
uint8 poolRebalanceLeafCount,
bytes32 poolRebalanceRoot,
bytes32 relayerRefundRoot,
bytes32 slowRelayRoot
) external;
function protocolFeeCaptureAddress() external view returns (address);
function protocolFeeCapturePct() external view returns (uint256);
function relaySpokePoolAdminFunction(uint256 chainId, bytes memory functionData) external;
function removeLiquidity(address l1Token, uint256 lpTokenAmount, bool sendEth) external;
function renounceOwnership() external;
function rootBundleProposal()
external
view
returns (
bytes32 poolRebalanceRoot,
bytes32 relayerRefundRoot,
bytes32 slowRelayRoot,
uint256 claimedBitMap,
address proposer,
uint8 unclaimedPoolRebalanceLeafCount,
uint32 challengePeriodEndTimestamp
);
function setBond(address newBondToken, uint256 newBondAmount) external;
function setCrossChainContracts(uint256 l2ChainId, address adapter, address spokePool) external;
function setCurrentTime(uint256 time) external;
function setDepositRoute(
uint256 originChainId,
uint256 destinationChainId,
address originToken,
bool depositsEnabled
) external;
function setIdentifier(bytes32 newIdentifier) external;
function setLiveness(uint32 newLiveness) external;
function setPaused(bool pause) external;
function setPoolRebalanceRoute(uint256 destinationChainId, address l1Token, address destinationToken) external;
function setProtocolFeeCapture(address newProtocolFeeCaptureAddress, uint256 newProtocolFeeCapturePct) external;
function sync(address l1Token) external;
function timerAddress() external view returns (address);
function transferOwnership(address newOwner) external;
function unclaimedAccumulatedProtocolFees(address) external view returns (uint256);
function weth() external view returns (address);
}
ICurveAddressProvider.sol 23 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
// Implementation: https://etherscan.io/address/0x0000000022d53366457f9d5e68ec105046fc4383#readContract
interface ICurveAddressProvider {
function get_registry() external view returns(address);
function get_address(uint256 _id) external view returns(address);
}
ICurveCalculator.sol 46 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
// Implementation: https://etherscan.io/address/0xc1DB00a8E5Ef7bfa476395cdbcc98235477cDE4E#readContract
interface ICurveCalculator {
function get_dx(
int128 n_coins,
uint256[8] memory balances,
uint256 amp,
uint256 fee,
uint256[8] memory rates,
uint256[8] memory precisions,
bool underlying,
int128 i,
int128 j,
uint256 dy
) external view returns(uint256);
function get_dy(
int128 n_coins,
uint256[8] memory balances,
uint256 amp,
uint256 fee,
uint256[8] memory rates,
uint256[8] memory precisions,
bool underlying,
int128 i,
int128 j,
uint256 dx
) external view returns(uint256);
}
ICurvePool.sol 34 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
// Implementation: https://etherscan.io/address/0x8e764bE4288B842791989DB5b8ec067279829809#writeContract
interface ICurvePool {
function exchange(
int128 i,
int128 j,
uint256 dx,
uint256 min_dy
) external payable returns (uint256);
function get_dy(
int128 i,
int128 j,
uint256 dx
) external view returns (uint256);
}
ICurvePoolRegistry.sol 30 lines
/*
Copyright 2022 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
// Implementation: https://etherscan.io/address/0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5#readContract
interface ICurvePoolRegistry {
// amplification factor
function get_A(address _pool) external view returns(uint256);
function get_balances(address _pool) external view returns(uint256[8] memory);
function get_coins(address _pool) external view returns(address[8] memory);
function get_coin_indices(address _pool, address _from, address _to) external view returns(int128, int128, bool);
function get_decimals(address _pool) external view returns(uint256[8] memory);
function get_n_coins(address _pool) external view returns(uint256[2] memory);
function get_fees(address _pool) external view returns(uint256[2] memory);
function get_rates(address _pool) external view returns(uint256[8] memory);
}
IPendleMarketV3.sol 86 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface IPendleMarketV3 {
struct MarketState {
int256 totalPt;
int256 totalSy;
int256 totalLp;
address treasury;
int256 scalarRoot;
uint256 expiry;
uint256 lnFeeRateRoot;
uint256 reserveFeePercent;
uint256 lastLnImpliedRate;
}
function DOMAIN_SEPARATOR() external view returns (bytes32);
function _storage()
external
view
returns (
int128 totalPt,
int128 totalSy,
uint96 lastLnImpliedRate,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext
);
function activeBalance(address) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function burn(address receiverSy, address receiverPt, uint256 netLpToBurn)
external
returns (uint256 netSyOut, uint256 netPtOut);
function decimals() external view returns (uint8);
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
function expiry() external view returns (uint256);
function factory() external view returns (address);
function getNonOverrideLnFeeRateRoot() external view returns (uint80);
function getRewardTokens() external view returns (address[] memory);
function increaseObservationsCardinalityNext(uint16 cardinalityNext) external;
function isExpired() external view returns (bool);
function lastRewardBlock() external view returns (uint256);
function mint(address receiver, uint256 netSyDesired, uint256 netPtDesired)
external
returns (uint256 netLpOut, uint256 netSyUsed, uint256 netPtUsed);
function name() external view returns (string memory);
function nonces(address owner) external view returns (uint256);
function observations(uint256)
external
view
returns (uint32 blockTimestamp, uint216 lnImpliedRateCumulative, bool initialized);
function observe(uint32[] memory secondsAgos) external view returns (uint216[] memory lnImpliedRateCumulative);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function readState(address router) external view returns (MarketState memory market);
function readTokens() external view returns (address _SY, address _PT, address _YT);
function redeemRewards(address user) external returns (uint256[] memory);
function rewardState(address) external view returns (uint128 index, uint128 lastBalance);
function skim() external;
function swapExactPtForSy(address receiver, uint256 exactPtIn, bytes memory data)
external
returns (uint256 netSyOut, uint256 netSyFee);
function swapSyForExactPt(address receiver, uint256 exactPtOut, bytes memory data)
external
returns (uint256 netSyIn, uint256 netSyFee);
function symbol() external view returns (string memory);
function totalActiveSupply() external view returns (uint256);
function totalSupply() external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function userReward(address, address) external view returns (uint128 index, uint128 accrued);
}
IPendlePrincipalToken.sol 39 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface IPendlePrincipalToken {
function DOMAIN_SEPARATOR() external view returns (bytes32);
function SY() external view returns (address);
function YT() external view returns (address);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function burnByYT(address user, uint256 amount) external;
function decimals() external view returns (uint8);
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
function expiry() external view returns (uint256);
function factory() external view returns (address);
function initialize(address _YT) external;
function isExpired() external view returns (bool);
function mintByYT(address user, uint256 amount) external;
function name() external view returns (string memory);
function nonces(address owner) external view returns (uint256);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
IPendleStandardizedYield.sol 73 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface IPendleStandardizedYield {
function DOMAIN_SEPARATOR() external view returns (bytes32);
function accruedRewards(address) external view returns (uint256[] memory rewardAmounts);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function assetInfo() external view returns (uint8, address, uint8);
function balanceOf(address account) external view returns (uint256);
function claimOwnership() external;
function claimRewards(address) external returns (uint256[] memory rewardAmounts);
function decimals() external view returns (uint8);
function deposit(address receiver, address tokenIn, uint256 amountTokenToDeposit, uint256 minSharesOut)
external
payable
returns (uint256 amountSharesOut);
function eETH() external view returns (address);
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
function exchangeRate() external view returns (uint256);
function getRewardTokens() external view returns (address[] memory rewardTokens);
function getTokensIn() external view returns (address[] memory res);
function getTokensOut() external view returns (address[] memory res);
function isValidTokenIn(address token) external view returns (bool);
function isValidTokenOut(address token) external view returns (bool);
function liquidityPool() external view returns (address);
function name() external view returns (string memory);
function nonces(address owner) external view returns (uint256);
function owner() external view returns (address);
function pause() external;
function paused() external view returns (bool);
function pendingOwner() external view returns (address);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function previewDeposit(address tokenIn, uint256 amountTokenToDeposit)
external
view
returns (uint256 amountSharesOut);
function previewRedeem(address tokenOut, uint256 amountSharesToRedeem)
external
view
returns (uint256 amountTokenOut);
function redeem(
address receiver,
uint256 amountSharesToRedeem,
address tokenOut,
uint256 minTokenOut,
bool burnFromInternalBalance
) external returns (uint256 amountTokenOut);
function referee() external view returns (address);
function rewardIndexesCurrent() external returns (uint256[] memory indexes);
function rewardIndexesStored() external view returns (uint256[] memory indexes);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function transferOwnership(address newOwner, bool direct, bool renounce) external;
function unpause() external;
function weETH() external view returns (address);
function yieldToken() external view returns (address);
}
IRateProvider.sol 25 lines
/*
Copyright 2023 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
/**
* https://github.com/balancer-labs/metastable-rate-providers/blob/master/contracts/interfaces/IRateProvider.sol
**/
interface IRateProvider {
function getRate() external view returns (uint256);
}
IRsEthAdapter.sol 11 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
interface IRsEthAdapter {
function depositRsETH(uint256 rsETHAmount, string memory referralId) external;
function getRSETHWithERC20(address asset, uint256 depositAmount, string memory referralId) external;
function getRSETHWithETH(string memory referralId) external payable;
function lrtDepositPool() external view returns (address);
function rsETH() external view returns (address);
function vault() external view returns (address);
}
IStETH.sol 6 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
interface IStETH {
function submit(address _referral) external payable returns (uint256);
}
ISwapRouter02.sol 127 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface ISwapRouter02 {
struct IncreaseLiquidityParams {
address token0;
address token1;
uint256 tokenId;
uint256 amount0Min;
uint256 amount1Min;
}
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
}
struct ExactInputParams {
bytes path;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
}
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
struct ExactOutputParams {
bytes path;
address recipient;
uint256 amountOut;
uint256 amountInMaximum;
}
struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}
function WETH9() external view returns (address);
function approveMax(address token) external payable;
function approveMaxMinusOne(address token) external payable;
function approveZeroThenMax(address token) external payable;
function approveZeroThenMaxMinusOne(address token) external payable;
function callPositionManager(bytes memory data) external payable returns (bytes memory result);
function checkOracleSlippage(
bytes[] memory paths,
uint128[] memory amounts,
uint24 maximumTickDivergence,
uint32 secondsAgo
) external view;
function checkOracleSlippage(bytes memory path, uint24 maximumTickDivergence, uint32 secondsAgo) external view;
function exactInput(ExactInputParams memory params) external payable returns (uint256 amountOut);
function exactInputSingle(ExactInputSingleParams memory params) external payable returns (uint256 amountOut);
function exactOutput(ExactOutputParams memory params) external payable returns (uint256 amountIn);
function exactOutputSingle(ExactOutputSingleParams memory params) external payable returns (uint256 amountIn);
function factory() external view returns (address);
function factoryV2() external view returns (address);
function getApprovalType(address token, uint256 amount) external returns (uint8);
function increaseLiquidity(IncreaseLiquidityParams memory params) external payable returns (bytes memory result);
function mint(MintParams memory params) external payable returns (bytes memory result);
function multicall(bytes32 previousBlockhash, bytes[] memory data) external payable returns (bytes[] memory);
function multicall(uint256 deadline, bytes[] memory data) external payable returns (bytes[] memory);
function multicall(bytes[] memory data) external payable returns (bytes[] memory results);
function positionManager() external view returns (address);
function pull(address token, uint256 value) external payable;
function refundETH() external payable;
function selfPermit(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external
payable;
function selfPermitAllowed(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)
external
payable;
function selfPermitAllowedIfNecessary(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)
external
payable;
function selfPermitIfNecessary(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external
payable;
function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] memory path, address to)
external
payable
returns (uint256 amountOut);
function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, address[] memory path, address to)
external
payable
returns (uint256 amountIn);
function sweepToken(address token, uint256 amountMinimum, address recipient) external payable;
function sweepToken(address token, uint256 amountMinimum) external payable;
function sweepTokenWithFee(address token, uint256 amountMinimum, uint256 feeBips, address feeRecipient)
external
payable;
function sweepTokenWithFee(
address token,
uint256 amountMinimum,
address recipient,
uint256 feeBips,
address feeRecipient
) external payable;
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes memory _data) external;
function unwrapWETH9(uint256 amountMinimum, address recipient) external payable;
function unwrapWETH9(uint256 amountMinimum) external payable;
function unwrapWETH9WithFee(uint256 amountMinimum, address recipient, uint256 feeBips, address feeRecipient)
external
payable;
function unwrapWETH9WithFee(uint256 amountMinimum, uint256 feeBips, address feeRecipient) external payable;
function wrapETH(uint256 value) external payable;
}
INonfungiblePositionManager.sol 144 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
/// @title Non-fungible token for positions
/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred
/// and authorized.
/// https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/INonfungiblePositionManager.sol
interface INonfungiblePositionManager {
/// @notice Returns the position information associated with a given token ID.
/// @dev Throws if the token ID is not valid.
/// @param tokenId The ID of the token that represents the position
/// @return nonce The nonce for permits
/// @return operator The address that is approved for spending
/// @return token0 The address of the token0 for a specific pool
/// @return token1 The address of the token1 for a specific pool
/// @return fee The fee associated with the pool
/// @return tickLower The lower end of the tick range for the position
/// @return tickUpper The higher end of the tick range for the position
/// @return liquidity The liquidity of the position
/// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position
/// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position
/// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation
/// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
/// @notice Creates a new position wrapped in a NFT
/// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized
/// a method does not exist, i.e. the pool is assumed to be initialized.
/// @param params The params necessary to mint a position, encoded as `MintParams` in calldata
/// @return tokenId The ID of the token that represents the minted position
/// @return liquidity The amount of liquidity for this position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function mint(MintParams calldata params)
external
payable
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
);
struct IncreaseLiquidityParams {
uint256 tokenId;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
/// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`
/// @param params tokenId The ID of the token for which liquidity is being increased,
/// amount0Desired The desired amount of token0 to be spent,
/// amount1Desired The desired amount of token1 to be spent,
/// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,
/// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,
/// deadline The time by which the transaction must be included to effect the change
/// @return liquidity The new liquidity amount as a result of the increase
/// @return amount0 The amount of token0 to acheive resulting liquidity
/// @return amount1 The amount of token1 to acheive resulting liquidity
function increaseLiquidity(IncreaseLiquidityParams calldata params)
external
payable
returns (
uint128 liquidity,
uint256 amount0,
uint256 amount1
);
struct DecreaseLiquidityParams {
uint256 tokenId;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
/// @notice Decreases the amount of liquidity in a position and accounts it to the position
/// @param params tokenId The ID of the token for which liquidity is being decreased,
/// amount The amount by which liquidity will be decreased,
/// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,
/// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,
/// deadline The time by which the transaction must be included to effect the change
/// @return amount0 The amount of token0 accounted to the position's tokens owed
/// @return amount1 The amount of token1 accounted to the position's tokens owed
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1);
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
/// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
/// @param params tokenId The ID of the NFT for which tokens are being collected,
/// recipient The account that should receive the tokens,
/// amount0Max The maximum amount of token0 to collect,
/// amount1Max The maximum amount of token1 to collect
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
/// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens
/// must be collected first.
/// @param tokenId The ID of the token that is being burned
function burn(uint256 tokenId) external payable;
}
IUniswapV3Pool.sol 131 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
/// https://github.com/Uniswap/v3-core/blob/main/contracts/interfaces/pool/IUniswapV3PoolActions.sol
interface IUniswapV3Pool {
/// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
/// when accessed externally.
/// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
/// tick The current tick of the pool, i.e. according to the last tick transition that was run.
/// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
/// boundary.
/// observationIndex The index of the last oracle observation that was written,
/// observationCardinality The current maximum number of observations stored in the pool,
/// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
/// feeProtocol The protocol fee for both tokens of the pool.
/// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
/// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
/// unlocked Whether the pool is currently locked to reentrancy
function slot0()
external
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);
/// @notice Sets the initial price for the pool
/// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
/// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
function initialize(uint160 sqrtPriceX96) external;
/// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
/// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
/// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
/// on tickLower, tickUpper, the amount of liquidity, and the current price.
/// @param recipient The address for which the liquidity will be created
/// @param tickLower The lower tick of the position in which to add liquidity
/// @param tickUpper The upper tick of the position in which to add liquidity
/// @param amount The amount of liquidity to mint
/// @param data Any data that should be passed through to the callback
/// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
/// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount,
bytes calldata data
) external returns (uint256 amount0, uint256 amount1);
/// @notice Collects tokens owed to a position
/// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
/// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
/// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
/// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
/// @param recipient The address which should receive the fees collected
/// @param tickLower The lower tick of the position for which to collect fees
/// @param tickUpper The upper tick of the position for which to collect fees
/// @param amount0Requested How much token0 should be withdrawn from the fees owed
/// @param amount1Requested How much token1 should be withdrawn from the fees owed
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount0Requested,
uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);
/// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
/// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
/// @dev Fees must be collected separately via a call to #collect
/// @param tickLower The lower tick of the position for which to burn liquidity
/// @param tickUpper The upper tick of the position for which to burn liquidity
/// @param amount How much liquidity to burn
/// @return amount0 The amount of token0 sent to the recipient
/// @return amount1 The amount of token1 sent to the recipient
function burn(
int24 tickLower,
int24 tickUpper,
uint128 amount
) external returns (uint256 amount0, uint256 amount1);
/// @notice Swap token0 for token1, or token1 for token0
/// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
/// @param recipient The address to receive the output of the swap
/// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
/// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
/// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
/// value after the swap. If one for zero, the price cannot be greater than this value after the swap
/// @param data Any data to be passed through to the callback
/// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
/// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
/// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
/// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
/// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
/// with 0 amount{0,1} and sending the donation amount(s) from the callback
/// @param recipient The address which will receive the token0 and token1 amounts
/// @param amount0 The amount of token0 to send
/// @param amount1 The amount of token1 to send
/// @param data Any data to be passed through to the callback
function flash(
address recipient,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external;
/// @notice Increase the maximum number of price and liquidity observations that this pool will store
/// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
/// the input observationCardinalityNext.
/// @param observationCardinalityNext The desired minimum number of observations for the pool to store
function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}
IAaveLeverageModule.sol 23 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "./ISetToken.sol";
interface IAaveLeverageModule {
function sync(ISetToken _setToken) external virtual;
}
IAaveOracle.sol 18 lines
pragma solidity 0.6.10;
interface IAaveOracle {
event AssetSourceUpdated(address indexed asset, address indexed source);
event BaseCurrencySet(address indexed baseCurrency, uint256 baseCurrencyUnit);
event FallbackOracleUpdated(address indexed fallbackOracle);
function ADDRESSES_PROVIDER() external view returns (address);
function BASE_CURRENCY() external view returns (address);
function BASE_CURRENCY_UNIT() external view returns (uint256);
function getAssetPrice(address asset) external view returns (uint256);
function getAssetsPrices(address[] memory assets) external view returns (uint256[] memory);
function getFallbackOracle() external view returns (address);
function getSourceOfAsset(address asset) external view returns (address);
function setAssetSources(address[] memory assets, address[] memory sources) external;
function setFallbackOracle(address fallbackOracle) external;
}
IAaveV3LeverageModule.sol 24 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ILeverageModule } from "./ILeverageModule.sol";
import { ISetToken } from "./ISetToken.sol";
interface IAaveV3LeverageModule is ILeverageModule {
function setEModeCategory(ISetToken _setToken, uint8 _categoryId) external virtual;
}
IAdapter.sol 26 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IBaseManager } from "./IBaseManager.sol";
interface IAdapter {
function manager() external view returns (IBaseManager);
}
IAerodromeRouter.sol 441 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
interface IAerodromeRouter {
struct Route {
address from;
address to;
bool stable;
address factory;
}
/// @notice Address of FactoryRegistry.sol
function factoryRegistry() external view returns (address);
/// @notice Address of Protocol PoolFactory.sol
function defaultFactory() external view returns (address);
/// @notice Address of Voter.sol
function voter() external view returns (address);
/// @notice Interface of WETH contract used for WETH => ETH wrapping/unwrapping
function weth() external view returns (address);
/// @dev Represents Ether. Used by zapper to determine whether to return assets as ETH/WETH.
function ETHER() external view returns (address);
/// @dev Struct containing information necessary to zap in and out of pools
/// @param tokenA .
/// @param tokenB .
/// @param stable Stable or volatile pool
/// @param factory factory of pool
/// @param amountOutMinA Minimum amount expected from swap leg of zap via routesA
/// @param amountOutMinB Minimum amount expected from swap leg of zap via routesB
/// @param amountAMin Minimum amount of tokenA expected from liquidity leg of zap
/// @param amountBMin Minimum amount of tokenB expected from liquidity leg of zap
struct Zap {
address tokenA;
address tokenB;
bool stable;
address factory;
uint256 amountOutMinA;
uint256 amountOutMinB;
uint256 amountAMin;
uint256 amountBMin;
}
/// @notice Sort two tokens by which address value is less than the other
/// @param tokenA Address of token to sort
/// @param tokenB Address of token to sort
/// @return token0 Lower address value between tokenA and tokenB
/// @return token1 Higher address value between tokenA and tokenB
function sortTokens(address tokenA, address tokenB) external pure returns (address token0, address token1);
/// @notice Calculate the address of a pool by its' factory.
/// Used by all Router functions containing a `Route[]` or `_factory` argument.
/// Reverts if _factory is not approved by the FactoryRegistry
/// @dev Returns a randomly generated address for a nonexistent pool
/// @param tokenA Address of token to query
/// @param tokenB Address of token to query
/// @param stable True if pool is stable, false if volatile
/// @param _factory Address of factory which created the pool
function poolFor(
address tokenA,
address tokenB,
bool stable,
address _factory
) external view returns (address pool);
/// @notice Fetch and sort the reserves for a pool
/// @param tokenA .
/// @param tokenB .
/// @param stable True if pool is stable, false if volatile
/// @param _factory Address of PoolFactory for tokenA and tokenB
/// @return reserveA Amount of reserves of the sorted token A
/// @return reserveB Amount of reserves of the sorted token B
function getReserves(
address tokenA,
address tokenB,
bool stable,
address _factory
) external view returns (uint256 reserveA, uint256 reserveB);
/// @notice Perform chained getAmountOut calculations on any number of pools
function getAmountsOut(uint256 amountIn, Route[] memory routes) external view returns (uint256[] memory amounts);
// **** ADD LIQUIDITY ****
/// @notice Quote the amount deposited into a Pool
/// @param tokenA .
/// @param tokenB .
/// @param stable True if pool is stable, false if volatile
/// @param _factory Address of PoolFactory for tokenA and tokenB
/// @param amountADesired Amount of tokenA desired to deposit
/// @param amountBDesired Amount of tokenB desired to deposit
/// @return amountA Amount of tokenA to actually deposit
/// @return amountB Amount of tokenB to actually deposit
/// @return liquidity Amount of liquidity token returned from deposit
function quoteAddLiquidity(
address tokenA,
address tokenB,
bool stable,
address _factory,
uint256 amountADesired,
uint256 amountBDesired
) external view returns (uint256 amountA, uint256 amountB, uint256 liquidity);
/// @notice Quote the amount of liquidity removed from a Pool
/// @param tokenA .
/// @param tokenB .
/// @param stable True if pool is stable, false if volatile
/// @param _factory Address of PoolFactory for tokenA and tokenB
/// @param liquidity Amount of liquidity to remove
/// @return amountA Amount of tokenA received
/// @return amountB Amount of tokenB received
function quoteRemoveLiquidity(
address tokenA,
address tokenB,
bool stable,
address _factory,
uint256 liquidity
) external view returns (uint256 amountA, uint256 amountB);
/// @notice Add liquidity of two tokens to a Pool
/// @param tokenA .
/// @param tokenB .
/// @param stable True if pool is stable, false if volatile
/// @param amountADesired Amount of tokenA desired to deposit
/// @param amountBDesired Amount of tokenB desired to deposit
/// @param amountAMin Minimum amount of tokenA to deposit
/// @param amountBMin Minimum amount of tokenB to deposit
/// @param to Recipient of liquidity token
/// @param deadline Deadline to receive liquidity
/// @return amountA Amount of tokenA to actually deposit
/// @return amountB Amount of tokenB to actually deposit
/// @return liquidity Amount of liquidity token returned from deposit
function addLiquidity(
address tokenA,
address tokenB,
bool stable,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);
/// @notice Add liquidity of a token and WETH (transferred as ETH) to a Pool
/// @param token .
/// @param stable True if pool is stable, false if volatile
/// @param amountTokenDesired Amount of token desired to deposit
/// @param amountTokenMin Minimum amount of token to deposit
/// @param amountETHMin Minimum amount of ETH to deposit
/// @param to Recipient of liquidity token
/// @param deadline Deadline to add liquidity
/// @return amountToken Amount of token to actually deposit
/// @return amountETH Amount of tokenETH to actually deposit
/// @return liquidity Amount of liquidity token returned from deposit
function addLiquidityETH(
address token,
bool stable,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);
// **** REMOVE LIQUIDITY ****
/// @notice Remove liquidity of two tokens from a Pool
/// @param tokenA .
/// @param tokenB .
/// @param stable True if pool is stable, false if volatile
/// @param liquidity Amount of liquidity to remove
/// @param amountAMin Minimum amount of tokenA to receive
/// @param amountBMin Minimum amount of tokenB to receive
/// @param to Recipient of tokens received
/// @param deadline Deadline to remove liquidity
/// @return amountA Amount of tokenA received
/// @return amountB Amount of tokenB received
function removeLiquidity(
address tokenA,
address tokenB,
bool stable,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB);
/// @notice Remove liquidity of a token and WETH (returned as ETH) from a Pool
/// @param token .
/// @param stable True if pool is stable, false if volatile
/// @param liquidity Amount of liquidity to remove
/// @param amountTokenMin Minimum amount of token to receive
/// @param amountETHMin Minimum amount of ETH to receive
/// @param to Recipient of liquidity token
/// @param deadline Deadline to receive liquidity
/// @return amountToken Amount of token received
/// @return amountETH Amount of ETH received
function removeLiquidityETH(
address token,
bool stable,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountETH);
/// @notice Remove liquidity of a fee-on-transfer token and WETH (returned as ETH) from a Pool
/// @param token .
/// @param stable True if pool is stable, false if volatile
/// @param liquidity Amount of liquidity to remove
/// @param amountTokenMin Minimum amount of token to receive
/// @param amountETHMin Minimum amount of ETH to receive
/// @param to Recipient of liquidity token
/// @param deadline Deadline to receive liquidity
/// @return amountETH Amount of ETH received
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
bool stable,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountETH);
// **** SWAP ****
/// @notice Swap one token for another
/// @param amountIn Amount of token in
/// @param amountOutMin Minimum amount of desired token received
/// @param routes Array of trade routes used in the swap
/// @param to Recipient of the tokens received
/// @param deadline Deadline to receive tokens
/// @return amounts Array of amounts returned per route
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
Route[] calldata routes,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
/// @notice Swap ETH for a token
/// @param amountOutMin Minimum amount of desired token received
/// @param routes Array of trade routes used in the swap
/// @param to Recipient of the tokens received
/// @param deadline Deadline to receive tokens
/// @return amounts Array of amounts returned per route
function swapExactETHForTokens(
uint256 amountOutMin,
Route[] calldata routes,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
/// @notice Swap a token for WETH (returned as ETH)
/// @param amountIn Amount of token in
/// @param amountOutMin Minimum amount of desired ETH
/// @param routes Array of trade routes used in the swap
/// @param to Recipient of the tokens received
/// @param deadline Deadline to receive tokens
/// @return amounts Array of amounts returned per route
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
Route[] calldata routes,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
/// @notice Swap one token for another without slippage protection
/// @return amounts Array of amounts to swap per route
/// @param routes Array of trade routes used in the swap
/// @param to Recipient of the tokens received
/// @param deadline Deadline to receive tokens
function UNSAFE_swapExactTokensForTokens(
uint256[] memory amounts,
Route[] calldata routes,
address to,
uint256 deadline
) external returns (uint256[] memory);
// **** SWAP (supporting fee-on-transfer tokens) ****
/// @notice Swap one token for another supporting fee-on-transfer tokens
/// @param amountIn Amount of token in
/// @param amountOutMin Minimum amount of desired token received
/// @param routes Array of trade routes used in the swap
/// @param to Recipient of the tokens received
/// @param deadline Deadline to receive tokens
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
Route[] calldata routes,
address to,
uint256 deadline
) external;
/// @notice Swap ETH for a token supporting fee-on-transfer tokens
/// @param amountOutMin Minimum amount of desired token received
/// @param routes Array of trade routes used in the swap
/// @param to Recipient of the tokens received
/// @param deadline Deadline to receive tokens
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
Route[] calldata routes,
address to,
uint256 deadline
) external payable;
/// @notice Swap a token for WETH (returned as ETH) supporting fee-on-transfer tokens
/// @param amountIn Amount of token in
/// @param amountOutMin Minimum amount of desired ETH
/// @param routes Array of trade routes used in the swap
/// @param to Recipient of the tokens received
/// @param deadline Deadline to receive tokens
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
Route[] calldata routes,
address to,
uint256 deadline
) external;
/// @notice Zap a token A into a pool (B, C). (A can be equal to B or C).
/// Supports standard ERC20 tokens only (i.e. not fee-on-transfer tokens etc).
/// Slippage is required for the initial swap.
/// Additional slippage may be required when adding liquidity as the
/// price of the token may have changed.
/// @param tokenIn Token you are zapping in from (i.e. input token).
/// @param amountInA Amount of input token you wish to send down routesA
/// @param amountInB Amount of input token you wish to send down routesB
/// @param zapInPool Contains zap struct information. See Zap struct.
/// @param routesA Route used to convert input token to tokenA
/// @param routesB Route used to convert input token to tokenB
/// @param to Address you wish to mint liquidity to.
/// @param stake Auto-stake liquidity in corresponding gauge.
/// @return liquidity Amount of LP tokens created from zapping in.
function zapIn(
address tokenIn,
uint256 amountInA,
uint256 amountInB,
Zap calldata zapInPool,
Route[] calldata routesA,
Route[] calldata routesB,
address to,
bool stake
) external payable returns (uint256 liquidity);
/// @notice Zap out a pool (B, C) into A.
/// Supports standard ERC20 tokens only (i.e. not fee-on-transfer tokens etc).
/// Slippage is required for the removal of liquidity.
/// Additional slippage may be required on the swap as the
/// price of the token may have changed.
/// @param tokenOut Token you are zapping out to (i.e. output token).
/// @param liquidity Amount of liquidity you wish to remove.
/// @param zapOutPool Contains zap struct information. See Zap struct.
/// @param routesA Route used to convert tokenA into output token.
/// @param routesB Route used to convert tokenB into output token.
function zapOut(
address tokenOut,
uint256 liquidity,
Zap calldata zapOutPool,
Route[] calldata routesA,
Route[] calldata routesB
) external;
/// @notice Used to generate params required for zapping in.
/// Zap in => remove liquidity then swap.
/// Apply slippage to expected swap values to account for changes in reserves in between.
/// @dev Output token refers to the token you want to zap in from.
/// @param tokenA .
/// @param tokenB .
/// @param stable .
/// @param _factory .
/// @param amountInA Amount of input token you wish to send down routesA
/// @param amountInB Amount of input token you wish to send down routesB
/// @param routesA Route used to convert input token to tokenA
/// @param routesB Route used to convert input token to tokenB
/// @return amountOutMinA Minimum output expected from swapping input token to tokenA.
/// @return amountOutMinB Minimum output expected from swapping input token to tokenB.
/// @return amountAMin Minimum amount of tokenA expected from depositing liquidity.
/// @return amountBMin Minimum amount of tokenB expected from depositing liquidity.
function generateZapInParams(
address tokenA,
address tokenB,
bool stable,
address _factory,
uint256 amountInA,
uint256 amountInB,
Route[] calldata routesA,
Route[] calldata routesB
) external view returns (uint256 amountOutMinA, uint256 amountOutMinB, uint256 amountAMin, uint256 amountBMin);
/// @notice Used to generate params required for zapping out.
/// Zap out => swap then add liquidity.
/// Apply slippage to expected liquidity values to account for changes in reserves in between.
/// @dev Output token refers to the token you want to zap out of.
/// @param tokenA .
/// @param tokenB .
/// @param stable .
/// @param _factory .
/// @param liquidity Amount of liquidity being zapped out of into a given output token.
/// @param routesA Route used to convert tokenA into output token.
/// @param routesB Route used to convert tokenB into output token.
/// @return amountOutMinA Minimum output expected from swapping tokenA into output token.
/// @return amountOutMinB Minimum output expected from swapping tokenB into output token.
/// @return amountAMin Minimum amount of tokenA expected from withdrawing liquidity.
/// @return amountBMin Minimum amount of tokenB expected from withdrawing liquidity.
function generateZapOutParams(
address tokenA,
address tokenB,
bool stable,
address _factory,
uint256 liquidity,
Route[] calldata routesA,
Route[] calldata routesB
) external view returns (uint256 amountOutMinA, uint256 amountOutMinB, uint256 amountAMin, uint256 amountBMin);
/// @notice Used by zapper to determine appropriate ratio of A to B to deposit liquidity. Assumes stable pool.
/// @dev Returns stable liquidity ratio of B to (A + B).
/// E.g. if ratio is 0.4, it means there is more of A than there is of B.
/// Therefore you should deposit more of token A than B.
/// @param tokenA tokenA of stable pool you are zapping into.
/// @param tokenB tokenB of stable pool you are zapping into.
/// @param factory Factory that created stable pool.
/// @return ratio Ratio of token0 to token1 required to deposit into zap.
function quoteStableLiquidityRatio(
address tokenA,
address tokenB,
address factory
) external view returns (uint256 ratio);
}
IAerodromeSlipstreamQuoter.sol 89 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
/// @title QuoterV2 Interface
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps.
/// @notice For each pool also tells you the number of initialized ticks crossed and the sqrt price of the pool after the swap.
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IAerodromeSlipstreamQuoter {
/// @notice Returns the amount out received for a given exact input swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool tick spacing
/// @param amountIn The amount of the first token to swap
/// @return amountOut The amount of the last token that would be received
/// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
/// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
/// @return gasEstimate The estimate of the gas that the swap consumes
function quoteExactInput(bytes memory path, uint256 amountIn)
external
returns (
uint256 amountOut,
uint160[] memory sqrtPriceX96AfterList,
uint32[] memory initializedTicksCrossedList,
uint256 gasEstimate
);
struct QuoteExactInputSingleParams {
address tokenIn;
address tokenOut;
uint256 amountIn;
int24 tickSpacing;
uint160 sqrtPriceLimitX96;
}
/// @notice Returns the amount out received for a given exact input but for a swap of a single pool
/// @param params The params for the quote, encoded as `QuoteExactInputSingleParams`
/// tokenIn The token being swapped in
/// tokenOut The token being swapped out
/// tickSpacing The tick spacing of the token pool to consider for the pair
/// amountIn The desired input amount
/// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountOut The amount of `tokenOut` that would be received
/// @return sqrtPriceX96After The sqrt price of the pool after the swap
/// @return initializedTicksCrossed The number of initialized ticks that the swap crossed
/// @return gasEstimate The estimate of the gas that the swap consumes
function quoteExactInputSingle(QuoteExactInputSingleParams memory params)
external
returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate);
/// @notice Returns the amount in required for a given exact output swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool tick spacing. Path must be provided in reverse order
/// @param amountOut The amount of the last token to receive
/// @return amountIn The amount of first token required to be paid
/// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
/// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
/// @return gasEstimate The estimate of the gas that the swap consumes
function quoteExactOutput(bytes memory path, uint256 amountOut)
external
returns (
uint256 amountIn,
uint160[] memory sqrtPriceX96AfterList,
uint32[] memory initializedTicksCrossedList,
uint256 gasEstimate
);
struct QuoteExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint256 amount;
int24 tickSpacing;
uint160 sqrtPriceLimitX96;
}
/// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
/// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams`
/// tokenIn The token being swapped in
/// tokenOut The token being swapped out
/// tickSpacing The tick spacing of the token pool to consider for the pair
/// amountOut The desired output amount
/// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
/// @return sqrtPriceX96After The sqrt price of the pool after the swap
/// @return initializedTicksCrossed The number of initialized ticks that the swap crossed
/// @return gasEstimate The estimate of the gas that the swap consumes
function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params)
external
returns (uint256 amountIn, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate);
}
IAerodromeSlipstreamRouter.sol 67 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via CL
interface IAerodromeSlipstreamRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
int24 tickSpacing;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
/// @return amountOut The amount of the received token
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
int24 tickSpacing;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);
struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}
IAirdropModule.sol 54 lines
/*
Copyright 2021 Index Cooperative.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { ISetToken } from "./ISetToken.sol";
interface IAirdropModule {
using AddressArrayUtils for address[];
struct AirdropSettings {
address[] airdrops; // Array of tokens manager is allowing to be absorbed
address feeRecipient; // Address airdrop fees are sent to
uint256 airdropFee; // Percentage in preciseUnits of airdrop sent to feeRecipient (1e16 = 1%)
bool anyoneAbsorb; // Boolean indicating if any address can call absorb or just the manager
}
struct AirdropReturnSettings {
address feeRecipient;
uint256 airdropFee;
bool anyoneAbsorb;
}
function initialize(ISetToken _setToken, AirdropSettings memory _airdropSettings) external;
function airdropSettings(ISetToken _setToken) external view returns(AirdropReturnSettings memory);
function batchAbsorb(ISetToken _setToken, address[] memory _tokens) external;
function absorb(ISetToken _setToken, IERC20 _token) external;
function addAirdrop(ISetToken _setToken, IERC20 _airdrop) external;
function removeAirdrop(ISetToken _setToken, IERC20 _airdrop) external;
function updateAnyoneAbsorb(ISetToken _setToken, bool _anyoneAbsorb) external;
function updateFeeRecipient(ISetToken _setToken, address _newFeeRecipient) external;
function updateAirdropFee(ISetToken _setToken, uint256 _newFee) external;
function removeModule() external;
function getAirdrops(ISetToken _setToken) external returns(address[] memory);
function isAirdropToken(ISetToken _setToken, IERC20 _token) external returns(bool);
}
IAToken.sol 67 lines
pragma solidity 0.6.10;
interface IAToken {
event Approval(address indexed owner, address indexed spender, uint256 value);
event BalanceTransfer(address indexed from, address indexed to, uint256 value, uint256 index);
event Burn(address indexed from, address indexed target, uint256 value, uint256 balanceIncrease, uint256 index);
event Initialized(
address indexed underlyingAsset,
address indexed pool,
address treasury,
address incentivesController,
uint8 aTokenDecimals,
string aTokenName,
string aTokenSymbol,
bytes params
);
event Mint(
address indexed caller, address indexed onBehalfOf, uint256 value, uint256 balanceIncrease, uint256 index
);
event Transfer(address indexed from, address indexed to, uint256 value);
function ATOKEN_REVISION() external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function EIP712_REVISION() external view returns (bytes memory);
function PERMIT_TYPEHASH() external view returns (bytes32);
function POOL() external view returns (address);
function RESERVE_TREASURY_ADDRESS() external view returns (address);
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address user) external view returns (uint256);
function burn(address from, address receiverOfUnderlying, uint256 amount, uint256 index) external;
function decimals() external view returns (uint8);
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
function getIncentivesController() external view returns (address);
function getPreviousIndex(address user) external view returns (uint256);
function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);
function handleRepayment(address user, address onBehalfOf, uint256 amount) external;
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function initialize(
address initializingPool,
address treasury,
address underlyingAsset,
address incentivesController,
uint8 aTokenDecimals,
string memory aTokenName,
string memory aTokenSymbol,
bytes memory params
) external;
function mint(address caller, address onBehalfOf, uint256 amount, uint256 index) external returns (bool);
function mintToTreasury(uint256 amount, uint256 index) external;
function name() external view returns (string memory);
function nonces(address owner) external view returns (uint256);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function rescueTokens(address token, address to, uint256 amount) external;
function scaledBalanceOf(address user) external view returns (uint256);
function scaledTotalSupply() external view returns (uint256);
function setIncentivesController(address controller) external;
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function transferOnLiquidation(address from, address to, uint256 value) external;
function transferUnderlyingTo(address target, uint256 amount) external;
}
IAuctionRebalanceModuleV1.sol 68 lines
/*
Copyright 2023 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ISetToken } from "./ISetToken.sol";
interface IAuctionRebalanceModuleV1 {
struct AuctionExecutionParams {
uint256 targetUnit;
string priceAdapterName;
bytes priceAdapterConfigData;
}
function startRebalance(
ISetToken _setToken,
IERC20 _quoteAsset,
address[] calldata _newComponents,
AuctionExecutionParams[] memory _newComponentsAuctionParams,
AuctionExecutionParams[] memory _oldComponentsAuctionParams,
bool _shouldLockSetToken,
uint256 _rebalanceDuration,
uint256 _initialPositionMultiplier
) external;
function bid(
ISetToken _setToken,
IERC20 _component,
uint256 _componentAmount,
uint256 _quoteAssetLimit
) external;
function raiseAssetTargets(ISetToken _setToken) external;
function unlock(ISetToken _setToken) external;
function setRaiseTargetPercentage(
ISetToken _setToken,
uint256 _raiseTargetPercentage
) external;
function setBidderStatus(
ISetToken _setToken,
address[] memory _bidders,
bool[] memory _statuses
) external;
function setAnyoneBid(ISetToken _setToken, bool _status) external;
function initialize(ISetToken _setToken) external;
}
IBalancerVault.sol 4 lines
pragma solidity 0.6.10;
interface IBalancerVault {
function flashLoan(address recipient, address[] memory tokens, uint256[] memory amounts, bytes memory userData) external;
}
IBaseManager.sol 34 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "./ISetToken.sol";
interface IBaseManager {
function setToken() external returns(ISetToken);
function methodologist() external returns(address);
function operator() external returns(address);
function interactManager(address _module, bytes calldata _encoded) external;
function transferTokens(address _token, address _destination, uint256 _amount) external;
}
IBasicIssuanceModule.sol 26 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity >=0.6.10;
import { ISetToken } from "./ISetToken.sol";
interface IBasicIssuanceModule {
function getRequiredComponentUnitsForIssue(
ISetToken _setToken,
uint256 _quantity
) external view returns(address[] memory, uint256[] memory);
function issue(ISetToken _setToken, uint256 _quantity, address _to) external;
function redeem(ISetToken _token, uint256 _quantity, address _to) external;
function initialize(ISetToken _setToken, address _preIssueHook) external;
}
ICErc20.sol 70 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title ICErc20
*
* Interface for interacting with Compound cErc20 tokens (e.g. Dai, USDC)
*/
interface ICErc20 is IERC20 {
function borrowBalanceCurrent(address _account) external returns (uint256);
function borrowBalanceStored(address _account) external view returns (uint256);
function balanceOfUnderlying(address _account) external returns (uint256);
/**
* Calculates the exchange rate from the underlying to the CToken
*
* @notice Accrue interest then return the up-to-date exchange rate
* @return Calculated exchange rate scaled by 1e18
*/
function exchangeRateCurrent() external returns (uint256);
function exchangeRateStored() external view returns (uint256);
function underlying() external view returns (address);
/**
* Sender supplies assets into the market and receives cTokens in exchange
*
* @notice Accrues interest whether or not the operation succeeds, unless reverted
* @param _mintAmount The amount of the underlying asset to supply
* @return uint256 0=success, otherwise a failure
*/
function mint(uint256 _mintAmount) external returns (uint256);
/**
* @notice Sender redeems cTokens in exchange for the underlying asset
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param _redeemTokens The number of cTokens to redeem into underlying
* @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function redeem(uint256 _redeemTokens) external returns (uint256);
/**
* @notice Sender redeems cTokens in exchange for a specified amount of underlying asset
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param _redeemAmount The amount of underlying to redeem
* @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function redeemUnderlying(uint256 _redeemAmount) external returns (uint256);
/**
* @notice Sender borrows assets from the protocol to their own address
* @param _borrowAmount The amount of the underlying asset to borrow
* @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function borrow(uint256 _borrowAmount) external returns (uint256);
/**
* @notice Sender repays their own borrow
* @param _repayAmount The amount to repay
* @return uint256 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function repayBorrow(uint256 _repayAmount) external returns (uint256);
}
ICErc20Delegator.sol 69 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface ICErc20Delegator {
function _acceptAdmin() external returns(uint256);
function _addReserves(uint256 addAmount) external returns(uint256);
function _reduceReserves(uint256 reduceAmount) external returns(uint256);
function _renounceAdminRights() external returns(uint256);
function _renounceFuseAdminRights() external returns(uint256);
function _resignImplementation() external;
function _setAdminFee(uint256 newAdminFeeMantissa) external returns(uint256);
function _setComptroller(address newComptroller) external returns(uint256);
function _setFuseFee() external returns(uint256);
function _setInterestRateModel(address newInterestRateModel) external returns(uint256);
function _setPendingAdmin(address newPendingAdmin) external returns(uint256);
function _setReserveFactor(uint256 newReserveFactorMantissa) external returns(uint256);
function _withdrawAdminFees(uint256 withdrawAmount) external returns(uint256);
function _withdrawFuseFees(uint256 withdrawAmount) external returns(uint256);
function accrualBlockNumber() external view returns(uint256);
function accrueInterest() external returns(uint256);
function admin() external view returns(address);
function adminFeeMantissa() external view returns(uint256);
function adminHasRights() external view returns(bool);
function allowance(address owner, address spender) external view returns(uint256);
function approve(address spender, uint256 amount) external returns(bool);
function balanceOf(address owner) external view returns(uint256);
function balanceOfUnderlying(address owner) external returns(uint256);
function borrow(uint256 borrowAmount) external returns(uint256);
function borrowBalanceCurrent(address account) external returns(uint256);
function borrowBalanceStored(address account) external view returns(uint256);
function borrowIndex() external view returns(uint256);
function borrowRatePerBlock() external view returns(uint256);
function comptroller() external view returns(address);
function decimals() external view returns(uint8);
function exchangeRateCurrent() external returns(uint256);
function exchangeRateStored() external view returns(uint256);
function fuseAdminHasRights() external view returns(bool);
function fuseFeeMantissa() external view returns(uint256);
function getAccountSnapshot(address account) external view returns(uint256, uint256, uint256, uint256);
function getCash() external view returns(uint256);
function implementation() external view returns(address);
function initialize(address comptroller_, address interestRateModel_, uint256 initialExchangeRateMantissa_, string memory name_, string memory symbol_, uint8 decimals_, uint256 reserveFactorMantissa_, uint256 adminFeeMantissa_) external;
function initialize(address underlying_, address comptroller_, address interestRateModel_, uint256 initialExchangeRateMantissa_, string memory name_, string memory symbol_, uint8 decimals_, uint256 reserveFactorMantissa_, uint256 adminFeeMantissa_) external;
function interestRateModel() external view returns(address);
function isCEther() external view returns(bool);
function isCToken() external view returns(bool);
function liquidateBorrow(address borrower, uint256 repayAmount, address cTokenCollateral) external returns(uint256);
function mint(uint256 mintAmount) external returns(uint256);
function name() external view returns(string memory);
function pendingAdmin() external view returns(address);
function redeem(uint256 redeemTokens) external returns(uint256);
function redeemUnderlying(uint256 redeemAmount) external returns(uint256);
function repayBorrow(uint256 repayAmount) external returns(uint256);
function repayBorrowBehalf(address borrower, uint256 repayAmount) external returns(uint256);
function reserveFactorMantissa() external view returns(uint256);
function seize(address liquidator, address borrower, uint256 seizeTokens) external returns(uint256);
function supplyRatePerBlock() external view returns(uint256);
function symbol() external view returns(string memory);
function totalAdminFees() external view returns(uint256);
function totalBorrows() external view returns(uint256);
function totalBorrowsCurrent() external returns(uint256);
function totalFuseFees() external view returns(uint256);
function totalReserves() external view returns(uint256);
function totalSupply() external view returns(uint256);
function transfer(address dst, uint256 amount) external returns(bool);
function transferFrom(address src, address dst, uint256 amount) external returns(bool);
function underlying() external view returns(address);
}
ICEther.sol 16 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface ICEther {
function mint() external payable;
function redeem(uint redeemTokens) external returns (uint);
function redeemUnderlying(uint redeemAmount) external returns (uint);
function borrow(uint borrowAmount) external returns (uint);
function getCash() external view returns (uint);
function exchangeRateStored() external view returns (uint);
function exchangeRateCurrent() external returns (uint);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
receive() external payable;
}
IChainlinkAggregatorV3.sol 13 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
interface IChainlinkAggregatorV3 {
function latestAnswer() external view returns (int256);
function latestRoundData() external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
IClaimAdapter.sol 65 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ISetToken } from "./ISetToken.sol";
pragma solidity 0.6.10;
/**
* @title IClaimAdapter
* @author Set Protocol
*
*/
interface IClaimAdapter {
/**
* Generates the calldata for claiming tokens from the rewars pool
*
* @param _setToken the set token that is owed the tokens
* @param _rewardPool the rewards pool to claim from
*
* @return _subject the rewards pool to call
* @return _value the amount of ether to send in the call
* @return _calldata the calldata to use
*/
function getClaimCallData(
ISetToken _setToken,
address _rewardPool
) external view returns(address _subject, uint256 _value, bytes memory _calldata);
/**
* Gets the amount of unclaimed rewards
*
* @param _setToken the set token that is owed the tokens
* @param _rewardPool the rewards pool to check
*
* @return uint256 the amount of unclaimed rewards
*/
function getRewardsAmount(ISetToken _setToken, address _rewardPool) external view returns(uint256);
/**
* Gets the rewards token
*
* @param _rewardPool the rewards pool to check
*
* @return IERC20 the reward token
*/
function getTokenAddress(address _rewardPool) external view returns(IERC20);
}
IClaimModule.sol 46 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "./ISetToken.sol";
interface IClaimModule {
function initialize(
ISetToken _setToken,
bool _anyoneClaim,
address[] calldata _rewardPools,
string[] calldata _integrationNames
) external;
function anyoneClaim(ISetToken _setToken) external view returns(bool);
function claim(ISetToken _setToken, address _rewardPool, string calldata _integrationName) external;
function batchClaim(ISetToken _setToken, address[] calldata _rewardPools, string[] calldata _integrationNames) external;
function updateAnyoneClaim(ISetToken _setToken, bool _anyoneClaim) external;
function addClaim(ISetToken _setToken, address _rewardPool, string calldata _integrationName) external;
function batchAddClaim(ISetToken _setToken, address[] calldata _rewardPools, string[] calldata _integrationNames) external;
function removeClaim(ISetToken _setToken, address _rewardPool, string calldata _integrationName) external;
function batchRemoveClaim(ISetToken _setToken, address[] calldata _rewardPools, string[] calldata _integrationNames) external;
function removeModule() external;
function getRewardPools(ISetToken _setToken) external returns(address[] memory);
function isRewardPool(ISetToken _setToken, address _rewardPool) external returns(bool);
function getRewardPoolClaims(ISetToken _setToken, address _rewardPool) external returns(address[] memory);
function isRewardPoolClaim(ISetToken _setToken, address _rewardPool, string calldata _integrationName) external returns (bool);
function getRewards(ISetToken _setToken, address _rewardPool, string calldata _integrationName) external returns (uint256);
}
ICompoundLeverageModule.sol 23 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "./ISetToken.sol";
interface ICompoundLeverageModule {
function sync(ISetToken _setToken, bool _shouldAccrueInterest) external virtual;
}
IComptroller.sol 31 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
/**
* @title IComptroller
*
* Interface for interacting with Compound Comptroller
*/
interface IComptroller {
/**
* @notice Add assets to be included in account liquidity calculation
* @param cTokens The list of addresses of the cToken markets to be enabled
* @return Success indicator for whether each corresponding market was entered
*/
function enterMarkets(address[] memory cTokens) external returns (uint256[] memory);
/**
* @notice Removes asset from sender's account liquidity calculation
* @dev Sender must not have an outstanding borrow balance in the asset,
* or be providing neccessary collateral for an outstanding borrow.
* @param cTokenAddress The address of the asset to be removed
* @return Whether or not the account successfully exited the market
*/
function exitMarket(address cTokenAddress) external returns (uint256);
function claimComp(address holder) external;
function markets(address cTokenAddress) external view returns (bool, uint256, bool);
}
IController.sol 28 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
interface IController {
function addSet(address _setToken) external;
function feeRecipient() external view returns(address);
function getModuleFee(address _module, uint256 _feeType) external view returns(uint256);
function isModule(address _module) external view returns(bool);
function isSet(address _setToken) external view returns(bool);
function isSystemContract(address _contractAddress) external view returns (bool);
function isResource(address _resource) external view returns(bool);
function resourceId(uint256 _id) external view returns(address);
function owner() external view returns(address);
function addFactory(address _factory) external;
function addModule(address _module) external;
}
IDebtIssuanceModule.sol 38 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity >=0.6.10;
import { ISetToken } from "./ISetToken.sol";
import { IManagerIssuanceHook } from "./IManagerIssuanceHook.sol";
interface IDebtIssuanceModule {
function getRequiredComponentIssuanceUnits(
ISetToken _setToken,
uint256 _quantity
) external view returns (address[] memory, uint256[] memory, uint256[] memory);
function getRequiredComponentRedemptionUnits(
ISetToken _setToken,
uint256 _quantity
) external view returns (address[] memory, uint256[] memory, uint256[] memory);
function issue(ISetToken _setToken, uint256 _quantity, address _to) external;
function redeem(ISetToken _token, uint256 _quantity, address _to) external;
function initialize(
ISetToken _setToken,
uint256 _maxManagerFee,
uint256 _managerIssueFee,
uint256 _managerRedeemFee,
address _feeRecipient,
IManagerIssuanceHook _managerIssuanceHook
) external;
}
IDelegatedManager.sol 53 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "./ISetToken.sol";
interface IDelegatedManager {
function interactManager(address _module, bytes calldata _encoded) external;
function initializeExtension() external;
function transferTokens(address _token, address _destination, uint256 _amount) external;
function updateOwnerFeeSplit(uint256 _newFeeSplit) external;
function updateOwnerFeeRecipient(address _newFeeRecipient) external;
function setMethodologist(address _newMethodologist) external;
function transferOwnership(address _owner) external;
function setToken() external view returns(ISetToken);
function owner() external view returns(address);
function methodologist() external view returns(address);
function operatorAllowlist(address _operator) external view returns(bool);
function assetAllowlist(address _asset) external view returns(bool);
function useAssetAllowlist() external view returns(bool);
function isAllowedAsset(address _asset) external view returns(bool);
function isPendingExtension(address _extension) external view returns(bool);
function isInitializedExtension(address _extension) external view returns(bool);
function getExtensions() external view returns(address[] memory);
function getOperators() external view returns(address[] memory);
function getAllowedAssets() external view returns(address[] memory);
function ownerFeeRecipient() external view returns(address);
function ownerFeeSplit() external view returns(uint256);
}
IERC4626.sol 14 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;
interface IERC4626 {
function asset() external view returns (address);
function deposit(uint256 assets_, address receiver_) external returns (uint256 shares_);
function mint(uint256 shares_, address receiver_) external returns (uint256 assets_);
function redeem(uint256 shares_, address receiver_, address owner_) external returns (uint256 assetsAfterFee_);
function withdraw(uint256 assets_, address receiver_, address owner_) external returns (uint256 shares_);
function previewDeposit(uint256 assets) external view returns (uint256);
function previewMint(uint256 shares) external view returns (uint256);
function previewRedeem(uint256 shares) external view returns (uint256);
function previewWithdraw(uint256 assets) external view returns (uint256);
}
IExtension.sol 26 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IBaseManager } from "./IBaseManager.sol";
interface IExtension {
function manager() external view returns (IBaseManager);
}
IFlashLoanReceiverV2.sol 25 lines
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.10;
import { ILendingPoolAddressesProviderV2 } from "./ILendingPoolAddressesProviderV2.sol";
import { ILendingPoolV2 } from "./ILendingPoolV2.sol";
/**
* @title IFlashLoanReceiverV2 interface
* @notice Interface for the Aave fee IFlashLoanReceiver.
* @author Aave
* @dev implement this interface to develop a flashloan-compatible flashLoanReceiver contract
**/
interface IFlashLoanReceiverV2 {
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool);
function ADDRESSES_PROVIDER() external view returns (ILendingPoolAddressesProviderV2);
function LENDING_POOL() external view returns (ILendingPoolV2);
}
IFlashLoanSimpleReceiver.sol 36 lines
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.6.10;
import {IPoolAddressesProvider} from "./IPoolAddressesProvider.sol";
import {IPool} from "./IPool.sol";
/**
* @title IFlashLoanSimpleReceiver
* @author Aave
* @notice Defines the basic interface of a flashloan-receiver contract.
* @dev Implement this interface to develop a flashloan-compatible flashLoanReceiver contract
*/
interface IFlashLoanSimpleReceiver {
/**
* @notice Executes an operation after receiving the flash-borrowed asset
* @dev Ensure that the contract can return the debt + premium, e.g., has
* enough funds to repay and has approved the Pool to pull the total amount
* @param asset The address of the flash-borrowed asset
* @param amount The amount of the flash-borrowed asset
* @param premium The fee of the flash-borrowed asset
* @param initiator The address of the flashloan initiator
* @param params The byte-encoded params passed when initiating the flashloan
* @return True if the execution of the operation succeeds, false otherwise
*/
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external returns (bool);
function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);
function POOL() external view returns (IPool);
}
IFlexibleLeverageStrategyExtension.sol 33 lines
/*
Copyright 2022 Index Cooperative.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface IFlexibleLeverageStrategyExtension {
enum ShouldRebalance {
NONE, // Indicates no rebalance action can be taken
REBALANCE, // Indicates rebalance() function can be successfully called
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
RIPCORD // Indicates ripcord() function can be successfully called
}
function shouldRebalance() external view returns (string[] memory, ShouldRebalance[] memory);
function rebalance(string memory _exchangeName) external;
function iterateRebalance(string memory _exchageName) external;
function ripcord(string memory _exchangeName) external;
function shouldRebalanceWithBounds(uint256 _customMinLeverageRatio, uint256 _customMaxLeverageRatio) external view returns (string[] memory, ShouldRebalance[] memory);
}
IFLIStrategyExtension.sol 49 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { FlexibleLeverageStrategyExtension } from "../adapters/FlexibleLeverageStrategyExtension.sol";
interface IFLIStrategyExtension {
function getStrategy() external view returns (FlexibleLeverageStrategyExtension.ContractSettings memory);
function getMethodology() external view returns (FlexibleLeverageStrategyExtension.MethodologySettings memory);
function getIncentive() external view returns (FlexibleLeverageStrategyExtension.IncentiveSettings memory);
function getExecution() external view returns (FlexibleLeverageStrategyExtension.ExecutionSettings memory);
function getExchangeSettings(string memory _exchangeName) external view returns (FlexibleLeverageStrategyExtension.ExchangeSettings memory);
function getEnabledExchanges() external view returns (string[] memory);
function getCurrentLeverageRatio() external view returns (uint256);
function getChunkRebalanceNotional(
string[] calldata _exchangeNames
)
external
view
returns(uint256[] memory sizes, address sellAsset, address buyAsset);
function shouldRebalance() external view returns(string[] memory, FlexibleLeverageStrategyExtension.ShouldRebalance[] memory);
function shouldRebalanceWithBounds(
uint256 _customMinLeverageRatio,
uint256 _customMaxLeverageRatio
)
external
view
returns(string[] memory, FlexibleLeverageStrategyExtension.ShouldRebalance[] memory);
}
IGeneralIndexModule.sol 86 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ISetToken } from "./ISetToken.sol";
interface IGeneralIndexModule {
function startRebalance(
ISetToken _setToken,
address[] calldata _newComponents,
uint256[] calldata _newComponentsTargetUnits,
uint256[] calldata _oldComponentsTargetUnits,
uint256 _positionMultiplier
)
external;
function trade(
ISetToken _setToken,
IERC20 _component,
uint256 _ethQuantityLimit
)
external;
function tradeRemainingWETH(
ISetToken _setToken,
IERC20 _component,
uint256 _minComponentReceived
)
external;
function raiseAssetTargets(ISetToken _setToken) external;
function setTradeMaximums(
ISetToken _setToken,
address[] memory _components,
uint256[] memory _tradeMaximums
)
external;
function setExchanges(
ISetToken _setToken,
address[] memory _components,
string[] memory _exchangeNames
)
external;
function setCoolOffPeriods(
ISetToken _setToken,
address[] memory _components,
uint256[] memory _coolOffPeriods
)
external;
function setExchangeData(
ISetToken _setToken,
address[] memory _components,
bytes[] memory _exchangeData
)
external;
function setRaiseTargetPercentage(ISetToken _setToken, uint256 _raiseTargetPercentage) external;
function setTraderStatus(
ISetToken _setToken,
address[] memory _traders,
bool[] memory _statuses
)
external;
function setAnyoneTrade(ISetToken _setToken, bool _status) external;
function initialize(ISetToken _setToken) external;
}
IGlobalExtension.sol 24 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
interface IGlobalExtension {
function removeExtension() external;
}
IGovernanceModule.sol 32 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "./ISetToken.sol";
interface IGovernanceModule {
function delegate(ISetToken _setToken, string memory _governanceName, address _delegatee) external;
function propose(ISetToken _setToken, string memory _governanceName, bytes memory _proposalData) external;
function register(ISetToken _setToken, string memory _governanceName) external;
function revoke(ISetToken _setToken, string memory _governanceName) external;
function vote(
ISetToken _setToken,
string memory _governanceName,
uint256 _proposalId,
bool _support,
bytes memory _data
)
external;
function initialize(ISetToken _setToken) external;
}
IIndexModule.sol 33 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "./ISetToken.sol";
interface IIndexModule {
function startRebalance(
address[] calldata _newComponents,
uint256[] calldata _newComponentsTargetUnits,
uint256[] calldata _oldComponentsTargetUnits,
uint256 _positionMultiplier
) external;
function setTradeMaximums(
address[] calldata _components,
uint256[] calldata _tradeMaximums
) external;
function setExchanges(
address[] calldata _components,
uint256[] calldata _exchanges
) external;
function setCoolOffPeriods(
address[] calldata _components,
uint256[] calldata _coolOffPeriods
) external;
function updateTraderStatus(address[] calldata _traders, bool[] calldata _statuses) external;
function updateAnyoneTrade(bool _status) external;
}
IIntegrationRegistry.sol 38 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
interface IIntegrationRegistry {
function addIntegration(
address _module,
string memory _id,
address _wrapper
) external;
function getIntegrationAdapter(address _module, string memory _id)
external
view
returns (address);
function getIntegrationAdapterWithHash(address _module, bytes32 _id)
external
view
returns (address);
function isValidIntegration(address _module, string memory _id) external view returns (bool);
}
IIssuanceModule.sol 41 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "./ISetToken.sol";
/**
* @title IDebtIssuanceModule
* @author Set Protocol
*
* Interface for interacting with Debt Issuance module interface.
*/
interface IIssuanceModule {
function updateIssueFee(ISetToken _setToken, uint256 _newIssueFee) external;
function updateRedeemFee(ISetToken _setToken, uint256 _newRedeemFee) external;
function updateFeeRecipient(ISetToken _setToken, address _newRedeemFee) external;
function initialize(
ISetToken _setToken,
uint256 _maxManagerFee,
uint256 _managerIssueFee,
uint256 _managerRedeemFee,
address _feeRecipient,
address _managerIssuanceHook
) external;
}
ILendingPoolAddressesProviderV2.sol 60 lines
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.10;
/**
* @title LendingPoolAddressesProvider contract
* @dev Main registry of addresses part of or connected to the protocol, including permissioned roles
* - Acting also as factory of proxies and admin of those, so with right to change its implementations
* - Owned by the Aave Governance
* @author Aave
**/
interface ILendingPoolAddressesProviderV2 {
event MarketIdSet(string newMarketId);
event LendingPoolUpdated(address indexed newAddress);
event ConfigurationAdminUpdated(address indexed newAddress);
event EmergencyAdminUpdated(address indexed newAddress);
event LendingPoolConfiguratorUpdated(address indexed newAddress);
event LendingPoolCollateralManagerUpdated(address indexed newAddress);
event PriceOracleUpdated(address indexed newAddress);
event LendingRateOracleUpdated(address indexed newAddress);
event ProxyCreated(bytes32 id, address indexed newAddress);
event AddressSet(bytes32 id, address indexed newAddress, bool hasProxy);
function getMarketId() external view returns (string memory);
function setMarketId(string calldata marketId) external;
function setAddress(bytes32 id, address newAddress) external;
function setAddressAsProxy(bytes32 id, address impl) external;
function getAddress(bytes32 id) external view returns (address);
function getLendingPool() external view returns (address);
function setLendingPoolImpl(address pool) external;
function getLendingPoolConfigurator() external view returns (address);
function setLendingPoolConfiguratorImpl(address configurator) external;
function getLendingPoolCollateralManager() external view returns (address);
function setLendingPoolCollateralManager(address manager) external;
function getPoolAdmin() external view returns (address);
function setPoolAdmin(address admin) external;
function getEmergencyAdmin() external view returns (address);
function setEmergencyAdmin(address admin) external;
function getPriceOracle() external view returns (address);
function setPriceOracle(address priceOracle) external;
function getLendingRateOracle() external view returns (address);
function setLendingRateOracle(address lendingRateOracle) external;
}
ILendingPoolV2.sol 410 lines
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.10;
pragma experimental ABIEncoderV2;
import {ILendingPoolAddressesProviderV2} from "./ILendingPoolAddressesProviderV2.sol";
import {DataTypes} from "../../external/contracts/aaveV2/lib/DataTypes.sol";
interface ILendingPoolV2 {
/**
* @dev Emitted on deposit()
* @param reserve The address of the underlying asset of the reserve
* @param user The address initiating the deposit
* @param onBehalfOf The beneficiary of the deposit, receiving the aTokens
* @param amount The amount deposited
* @param referral The referral code used
**/
event Deposit(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint16 indexed referral
);
/**
* @dev Emitted on withdraw()
* @param reserve The address of the underlyng asset being withdrawn
* @param user The address initiating the withdrawal, owner of aTokens
* @param to Address that will receive the underlying
* @param amount The amount to be withdrawn
**/
event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount);
/**
* @dev Emitted on borrow() and flashLoan() when debt needs to be opened
* @param reserve The address of the underlying asset being borrowed
* @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just
* initiator of the transaction on flashLoan()
* @param onBehalfOf The address that will be getting the debt
* @param amount The amount borrowed out
* @param borrowRateMode The rate mode: 1 for Stable, 2 for Variable
* @param borrowRate The numeric rate at which the user has borrowed
* @param referral The referral code used
**/
event Borrow(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint256 borrowRateMode,
uint256 borrowRate,
uint16 indexed referral
);
/**
* @dev Emitted on repay()
* @param reserve The address of the underlying asset of the reserve
* @param user The beneficiary of the repayment, getting his debt reduced
* @param repayer The address of the user initiating the repay(), providing the funds
* @param amount The amount repaid
**/
event Repay(
address indexed reserve,
address indexed user,
address indexed repayer,
uint256 amount
);
/**
* @dev Emitted on swapBorrowRateMode()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user swapping his rate mode
* @param rateMode The rate mode that the user wants to swap to
**/
event Swap(address indexed reserve, address indexed user, uint256 rateMode);
/**
* @dev Emitted on setUserUseReserveAsCollateral()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user enabling the usage as collateral
**/
event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user);
/**
* @dev Emitted on setUserUseReserveAsCollateral()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user enabling the usage as collateral
**/
event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user);
/**
* @dev Emitted on rebalanceStableBorrowRate()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user for which the rebalance has been executed
**/
event RebalanceStableBorrowRate(address indexed reserve, address indexed user);
/**
* @dev Emitted on flashLoan()
* @param target The address of the flash loan receiver contract
* @param initiator The address initiating the flash loan
* @param asset The address of the asset being flash borrowed
* @param amount The amount flash borrowed
* @param premium The fee flash borrowed
* @param referralCode The referral code used
**/
event FlashLoan(
address indexed target,
address indexed initiator,
address indexed asset,
uint256 amount,
uint256 premium,
uint16 referralCode
);
/**
* @dev Emitted when the pause is triggered.
*/
event Paused();
/**
* @dev Emitted when the pause is lifted.
*/
event Unpaused();
/**
* @dev Emitted when a borrower is liquidated. This event is emitted by the LendingPool via
* LendingPoolCollateral manager using a DELEGATECALL
* This allows to have the events in the generated ABI for LendingPool.
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param liquidatedCollateralAmount The amount of collateral received by the liiquidator
* @param liquidator The address of the liquidator
* @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
**/
event LiquidationCall(
address indexed collateralAsset,
address indexed debtAsset,
address indexed user,
uint256 debtToCover,
uint256 liquidatedCollateralAmount,
address liquidator,
bool receiveAToken
);
/**
* @dev Emitted when the state of a reserve is updated. NOTE: This event is actually declared
* in the ReserveLogic library and emitted in the updateInterestRates() function. Since the function is internal,
* the event will actually be fired by the LendingPool contract. The event is therefore replicated here so it
* gets added to the LendingPool ABI
* @param reserve The address of the underlying asset of the reserve
* @param liquidityRate The new liquidity rate
* @param stableBorrowRate The new stable borrow rate
* @param variableBorrowRate The new variable borrow rate
* @param liquidityIndex The new liquidity index
* @param variableBorrowIndex The new variable borrow index
**/
event ReserveDataUpdated(
address indexed reserve,
uint256 liquidityRate,
uint256 stableBorrowRate,
uint256 variableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex
);
/**
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC
* @param asset The address of the underlying asset to deposit
* @param amount The amount to be deposited
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function deposit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
/**
* @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
* @param asset The address of the underlying asset to withdraw
* @param amount The underlying amount to be withdrawn
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
* @param to Address that will receive the underlying, same as msg.sender if the user
* wants to receive it on his own wallet, or a different address if the beneficiary is a
* different wallet
* @return The final amount withdrawn
**/
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256);
/**
* @dev Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower
* already deposited enough collateral, or he was given enough allowance by a credit delegator on the
* corresponding debt token (StableDebtToken or VariableDebtToken)
* - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet
* and 100 stable/variable debt tokens, depending on the `interestRateMode`
* @param asset The address of the underlying asset to borrow
* @param amount The amount to be borrowed
* @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
* @param onBehalfOf Address of the user who will receive the debt. Should be the address of the borrower itself
* calling the function if he wants to borrow against his own collateral, or the address of the credit delegator
* if he has been given credit delegation allowance
**/
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode,
uint16 referralCode,
address onBehalfOf
) external;
/**
* @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned
* - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of the `onBehalfOf` address
* @param asset The address of the borrowed underlying asset previously borrowed
* @param amount The amount to repay
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
* @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable
* @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
* other borrower whose debt should be removed
* @return The final amount repaid
**/
function repay(
address asset,
uint256 amount,
uint256 rateMode,
address onBehalfOf
) external returns (uint256);
/**
* @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa
* @param asset The address of the underlying asset borrowed
* @param rateMode The rate mode that the user wants to swap to
**/
function swapBorrowRateMode(address asset, uint256 rateMode) external;
/**
* @dev Rebalances the stable interest rate of a user to the current stable rate defined on the reserve.
* - Users can be rebalanced if the following conditions are satisfied:
* 1. Usage ratio is above 95%
* 2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been
* borrowed at a stable rate and depositors are not earning enough
* @param asset The address of the underlying asset borrowed
* @param user The address of the user to be rebalanced
**/
function rebalanceStableBorrowRate(address asset, address user) external;
/**
* @dev Allows depositors to enable/disable a specific deposited asset as collateral
* @param asset The address of the underlying asset deposited
* @param useAsCollateral `true` if the user wants to use the deposit as collateral, `false` otherwise
**/
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;
/**
* @dev Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1
* - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives
* a proportionally amount of the `collateralAsset` plus a bonus to cover market risk
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
**/
function liquidationCall(
address collateralAsset,
address debtAsset,
address user,
uint256 debtToCover,
bool receiveAToken
) external;
/**
* @dev Allows smartcontracts to access the liquidity of the pool within one transaction,
* as long as the amount taken plus a fee is returned.
* IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept into consideration.
* For further details please visit https://developers.aave.com
* @param receiverAddress The address of the contract receiving the funds, implementing the IFlashLoanReceiver interface
* @param assets The addresses of the assets being flash-borrowed
* @param amounts The amounts amounts being flash-borrowed
* @param modes Types of the debt to open if the flash loan is not returned:
* 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
* 1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
* 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
* @param onBehalfOf The address that will receive the debt in the case of using on `modes` 1 or 2
* @param params Variadic packed params to pass to the receiver as extra information
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
/**
* @dev Returns the user account data across all the reserves
* @param user The address of the user
* @return totalCollateralETH the total collateral in ETH of the user
* @return totalDebtETH the total debt in ETH of the user
* @return availableBorrowsETH the borrowing power left of the user
* @return currentLiquidationThreshold the liquidation threshold of the user
* @return ltv the loan to value of the user
* @return healthFactor the current health factor of the user
**/
function getUserAccountData(address user)
external
view
returns (
uint256 totalCollateralETH,
uint256 totalDebtETH,
uint256 availableBorrowsETH,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
function initReserve(
address reserve,
address aTokenAddress,
address stableDebtAddress,
address variableDebtAddress,
address interestRateStrategyAddress
) external;
function setReserveInterestRateStrategyAddress(address reserve, address rateStrategyAddress)
external;
function setConfiguration(address reserve, uint256 configuration) external;
/**
* @dev Returns the configuration of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The configuration of the reserve
**/
function getConfiguration(address asset)
external
view
returns (DataTypes.ReserveConfigurationMap memory);
/**
* @dev Returns the configuration of the user across all the reserves
* @param user The user address
* @return The configuration of the user
**/
function getUserConfiguration(address user)
external
view
returns (DataTypes.UserConfigurationMap memory);
/**
* @dev Returns the normalized income normalized income of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The reserve's normalized income
*/
function getReserveNormalizedIncome(address asset) external view returns (uint256);
/**
* @dev Returns the normalized variable debt per unit of asset
* @param asset The address of the underlying asset of the reserve
* @return The reserve normalized variable debt
*/
function getReserveNormalizedVariableDebt(address asset) external view returns (uint256);
/**
* @dev Returns the state and configuration of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The state of the reserve
**/
function getReserveData(address asset) external view returns (DataTypes.ReserveData memory);
function finalizeTransfer(
address asset,
address from,
address to,
uint256 amount,
uint256 balanceFromAfter,
uint256 balanceToBefore
) external;
function getReservesList() external view returns (address[] memory);
function getAddressesProvider() external view returns (ILendingPoolAddressesProviderV2);
function setPause(bool val) external;
function paused() external view returns (bool);
}
ILeverageModule.sol 39 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "./ISetToken.sol";
interface ILeverageModule {
function sync(
ISetToken _setToken
) external;
function lever(
ISetToken _setToken,
address _borrowAsset,
address _collateralAsset,
uint256 _borrowQuantity,
uint256 _minReceiveQuantity,
string memory _tradeAdapterName,
bytes memory _tradeData
) external;
function delever(
ISetToken _setToken,
address _collateralAsset,
address _repayAsset,
uint256 _redeemQuantity,
uint256 _minRepayQuantity,
string memory _tradeAdapterName,
bytes memory _tradeData
) external;
function gulp(
ISetToken _setToken,
address _collateralAsset,
uint256 _minNotionalReceiveQuantity,
string memory _tradeAdapterName,
bytes memory _tradeData
) external;
}
IManagerCore.sol 26 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
interface IManagerCore {
function addManager(address _manager) external;
function isExtension(address _extension) external view returns(bool);
function isFactory(address _factory) external view returns(bool);
function isManager(address _manager) external view returns(bool);
function owner() external view returns(address);
}
IManagerIssuanceHook.sol 25 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "./ISetToken.sol";
interface IManagerIssuanceHook {
function invokePreIssueHook(ISetToken _setToken, uint256 _issueQuantity, address _sender, address _to) external;
function invokePreRedeemHook(ISetToken _setToken, uint256 _redeemQuantity, address _sender, address _to) external;
}
IMasterChef.sol 6 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
interface IMasterChef {
function userInfo(uint256 nr, address who) external view returns (uint256, uint256);
}
IMerkleDistributor.sol 18 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.10;
// Allows anyone to claim a token if they exist in a merkle root.
interface IMerkleDistributor {
// Returns the address of the token distributed by this contract.
function token() external view returns (address);
// Returns the merkle root of the merkle tree containing account balances available to claim.
function merkleRoot() external view returns (bytes32);
// Returns true if the index has been marked claimed.
function isClaimed(uint256 index) external view returns (bool);
// Claim the given amount of the token to the given address. Reverts if the inputs are invalid.
function claim(uint256 index, address account, uint256 amount, bytes32[] calldata merkleProof) external;
// This event is triggered whenever a call to #claim succeeds.
event Claimed(uint256 index, address account, uint256 amount);
}
IModule.sol 33 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
/**
* @title IModule
* @author Set Protocol
*
* Interface for interacting with Modules.
*/
interface IModule {
/**
* Called by a SetToken to notify that this module was removed from the Set token. Any logic can be included
* in case checks need to be made or state needs to be cleared.
*/
function removeModule() external;
}
IMorpho.sol 151 lines
// SPDX-License-bytes32entifier: UNLICENSED
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface IMorpho {
struct Authorization {
address authorizer;
address authorized;
bool isAuthorized;
uint256 nonce;
uint256 deadline;
}
struct MarketParams {
address loanToken;
address collateralToken;
address oracle;
address irm;
uint256 lltv;
}
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
event AccrueInterest(bytes32 indexed id, uint256 prevBorrowRate, uint256 interest, uint256 feeShares);
event Borrow(
bytes32 indexed id,
address caller,
address indexed onBehalf,
address indexed receiver,
uint256 assets,
uint256 shares
);
event CreateMarket(bytes32 indexed id, MarketParams marketParams);
event EnableIrm(address indexed irm);
event EnableLltv(uint256 lltv);
event FlashLoan(address indexed caller, address indexed token, uint256 assets);
event IncrementNonce(address indexed caller, address indexed authorizer, uint256 usedNonce);
event Liquidate(
bytes32 indexed id,
address indexed caller,
address indexed borrower,
uint256 repaidAssets,
uint256 repaidShares,
uint256 seizedAssets,
uint256 badDebtAssets,
uint256 badDebtShares
);
event Repay(bytes32 indexed id, address indexed caller, address indexed onBehalf, uint256 assets, uint256 shares);
event SetAuthorization(
address indexed caller, address indexed authorizer, address indexed authorized, bool newIsAuthorized
);
event SetFee(bytes32 indexed id, uint256 newFee);
event SetFeeRecipient(address indexed newFeeRecipient);
event SetOwner(address indexed newOwner);
event Supply(bytes32 indexed id, address indexed caller, address indexed onBehalf, uint256 assets, uint256 shares);
event SupplyCollateral(bytes32 indexed id, address indexed caller, address indexed onBehalf, uint256 assets);
event Withdraw(
bytes32 indexed id,
address caller,
address indexed onBehalf,
address indexed receiver,
uint256 assets,
uint256 shares
);
event WithdrawCollateral(
bytes32 indexed id, address caller, address indexed onBehalf, address indexed receiver, uint256 assets
);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function accrueInterest(MarketParams memory marketParams) external;
function borrow(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (uint256, uint256);
function createMarket(MarketParams memory marketParams) external;
function enableIrm(address irm) external;
function enableLltv(uint256 lltv) external;
function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory res);
function feeRecipient() external view returns (address);
function flashLoan(address token, uint256 assets, bytes memory data) external;
function idToMarketParams(bytes32)
external
view
returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv);
function isAuthorized(address, address) external view returns (bool);
function isIrmEnabled(address) external view returns (bool);
function isLltvEnabled(uint256) external view returns (bool);
function liquidate(
MarketParams memory marketParams,
address borrower,
uint256 seizedAssets,
uint256 repaidShares,
bytes memory data
) external returns (uint256, uint256);
function market(bytes32)
external
view
returns (
uint128 totalSupplyAssets,
uint128 totalSupplyShares,
uint128 totalBorrowAssets,
uint128 totalBorrowShares,
uint128 lastUpdate,
uint128 fee
);
function nonce(address) external view returns (uint256);
function owner() external view returns (address);
function position(bytes32, address)
external
view
returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral);
function repay(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes memory data
) external returns (uint256, uint256);
function setAuthorization(address authorized, bool newIsAuthorized) external;
function setAuthorizationWithSig(Authorization memory authorization, Signature memory signature) external;
function setFee(MarketParams memory marketParams, uint256 newFee) external;
function setFeeRecipient(address newFeeRecipient) external;
function setOwner(address newOwner) external;
function supply(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes memory data
) external returns (uint256, uint256);
function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data)
external;
function withdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (uint256, uint256);
function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
external;
}
IMorphoLeverageModule.sol 38 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "./ISetToken.sol";
import { IMorpho } from "./IMorpho.sol";
interface IMorphoLeverageModule {
function sync(
ISetToken _setToken
) external;
function lever(
ISetToken _setToken,
uint256 _borrowQuantityUnits,
uint256 _minReceiveQuantityUnits,
string memory _tradeAdapterName,
bytes memory _tradeData
) external;
function delever(
ISetToken _setToken,
uint256 _redeemQuantityUnits,
uint256 _minRepayQuantityUnits,
string memory _tradeAdapterName,
bytes memory _tradeData
) external;
function marketParams(ISetToken _setToken) external view returns (IMorpho.MarketParams memory);
function getCollateralAndBorrowBalances(
ISetToken _setToken
)
external
view
returns(uint256 collateralBalance, uint256 borrowBalance, uint256 borrowSharesU256);
}
IMorphoOracle.sol 14 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.6.10;
/// @title IOracle
/// @author Morpho Labs
/// @notice Interface that oracles used by Morpho must implement.
/// @dev It is the user's responsibility to select markets with safe oracles.
interface IMorphoOracle {
/// @notice Returns the price of 1 asset of collateral token quoted in 1 asset of loan token, scaled by 1e36.
/// @dev It corresponds to the price of 10**(collateral token decimals) assets of collateral token quoted in
/// 10**(loan token decimals) assets of loan token with `36 + loan token decimals - collateral token decimals`
/// decimals of precision.
function price() external view returns (uint256);
}
INAVIssuanceHook.sol 39 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "./ISetToken.sol";
interface INAVIssuanceHook {
function invokePreIssueHook(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity,
address _sender,
address _to
)
external;
function invokePreRedeemHook(
ISetToken _setToken,
uint256 _redeemQuantity,
address _sender,
address _to
)
external;
}
INAVIssuanceModule.sol 57 lines
/*
Copyright 2024 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "./ISetToken.sol";
interface INAVIssuanceModule {
function issue(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity,
uint256 _minSetTokenReceiveQuantity,
address _to
) external;
function redeem(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity,
uint256 _minReserveReceiveQuantity,
address _to
) external;
function isReserveAsset(
ISetToken _setToken,
address _asset
) external view returns(bool);
function getReserveAssets(address _setToken) external view returns (address[] memory);
function getExpectedSetTokenIssueQuantity(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity
) external view returns (uint256);
function getExpectedReserveRedeemQuantity(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity
) external view returns (uint256);
}
INotionalProxy.sol 125 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity ^0.6.10;
pragma experimental ABIEncoderV2;
/// @notice Different types of internal tokens
/// - UnderlyingToken: underlying asset for a cToken (except for Ether)
/// - cToken: Compound interest bearing token
/// - cETH: Special handling for cETH tokens
/// - Ether: the one and only
/// - NonMintable: tokens that do not have an underlying (therefore not cTokens)
enum TokenType {
UnderlyingToken,
cToken,
cETH,
Ether,
NonMintable
}
/// @notice Internal object that represents a token
struct Token {
address tokenAddress;
bool hasTransferFee;
int256 decimals;
TokenType tokenType;
uint256 maxCollateralBalance;
}
/// @dev Market object as represented in memory
struct MarketParameters {
bytes32 storageSlot;
uint256 maturity;
// Total amount of fCash available for purchase in the market.
int256 totalfCash;
// Total amount of cash available for purchase in the market.
int256 totalAssetCash;
// Total amount of liquidity tokens (representing a claim on liquidity) in the market.
int256 totalLiquidity;
// This is the implied rate that we use to smooth the anchor rate between trades.
uint256 lastImpliedRate;
// This is the oracle rate used to value fCash and prevent flash loan attacks
uint256 oracleRate;
// This is the timestamp of the previous trade
uint256 previousTradeTime;
}
interface INotionalProxy {
function getMaxCurrencyId() external view returns (uint16);
function getCurrencyId(address tokenAddress) external view returns (uint16 currencyId);
function getCurrency(uint16 currencyId)
external
view
returns (Token memory assetToken, Token memory underlyingToken);
function getActiveMarkets(uint16 currencyId) external view returns (MarketParameters[] memory);
function getfCashNotional(
address account,
uint16 currencyId,
uint256 maturity
) external view returns (int256);
function settleAccount(address account) external;
function getfCashLendFromDeposit(
uint16 currencyId,
uint256 depositAmountExternal,
uint256 maturity,
uint32 minLendRate,
uint256 blockTime,
bool useUnderlying
) external view returns (
uint88 fCashAmount,
uint8 marketIndex,
bytes32 encodedTrade
);
function getDepositFromfCashLend(
uint16 currencyId,
uint256 fCashAmount,
uint256 maturity,
uint32 minLendRate,
uint256 blockTime
) external view returns (
uint256 depositAmountUnderlying,
uint256 depositAmountAsset,
uint8 marketIndex,
bytes32 encodedTrade
);
function getPrincipalFromfCashBorrow(
uint16 currencyId,
uint256 fCashBorrow,
uint256 maturity,
uint32 maxBorrowRate,
uint256 blockTime
) external view returns (
uint256 borrowAmountUnderlying,
uint256 borrowAmountAsset,
uint8 marketIndex,
bytes32 encodedTrade
);
function getfCashBorrowFromPrincipal(
uint16 currencyId,
uint256 borrowedAmountExternal,
uint256 maturity,
uint32 maxBorrowRate,
uint256 blockTime,
bool useUnderlying
) external view returns (
uint88 fCashDebt,
uint8 marketIndex,
bytes32 encodedTrade
);
function initializeMarkets(
uint16 currencyId,
bool flag
) external;
}
INotionalTradeModule.sol 51 lines
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.6.10;
import { ISetToken } from "../interfaces/ISetToken.sol";
interface INotionalTradeModule {
function redeemMaturedPositions(ISetToken) external;
function initialize(ISetToken) external;
function updateAllowedSetToken(ISetToken, bool) external;
function owner() external view returns(address);
function settleAccount(address) external;
function setRedeemToUnderlying(ISetToken, bool) external;
function getFCashComponents(ISetToken _setToken) external view returns(address[] memory fCashComponents);
function mintFixedFCashForToken(
ISetToken _setToken,
uint16 _currencyId,
uint40 _maturity,
uint256 _mintAmount,
address _sendToken,
uint256 _maxSendAmount
) external returns(uint256);
function redeemFixedFCashForToken(
ISetToken _setToken,
uint16 _currencyId,
uint40 _maturity,
uint256 _redeemAmount,
address _receiveToken,
uint256 _minReceiveAmount
) external returns(uint256);
function mintFCashForFixedToken(
ISetToken _setToken,
uint16 _currencyId,
uint40 _maturity,
uint256 _minMintAmount,
address _sendToken,
uint256 _sendAmount
) external returns(uint256);
function redeemFCashForFixedToken(
ISetToken _setToken,
uint16 _currencyId,
uint40 _maturity,
uint256 _maxRedeemAmount,
address _receiveToken,
uint256 _receiveAmount,
uint256 _maxReceiveAmountDeviation
) external returns(uint256);
}
IPair.sol 53 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
interface IPair {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
IPool.sol 205 lines
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { DataTypes } from "./Datatypes.sol";
interface IPool {
event BackUnbacked(address indexed reserve, address indexed backer, uint256 amount, uint256 fee);
event Borrow(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint8 interestRateMode,
uint256 borrowRate,
uint16 indexed referralCode
);
event FlashLoan(
address indexed target,
address initiator,
address indexed asset,
uint256 amount,
uint8 interestRateMode,
uint256 premium,
uint16 indexed referralCode
);
event IsolationModeTotalDebtUpdated(address indexed asset, uint256 totalDebt);
event LiquidationCall(
address indexed collateralAsset,
address indexed debtAsset,
address indexed user,
uint256 debtToCover,
uint256 liquidatedCollateralAmount,
address liquidator,
bool receiveAToken
);
event MintUnbacked(
address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, uint16 indexed referralCode
);
event MintedToTreasury(address indexed reserve, uint256 amountMinted);
event RebalanceStableBorrowRate(address indexed reserve, address indexed user);
event Repay(
address indexed reserve, address indexed user, address indexed repayer, uint256 amount, bool useATokens
);
event ReserveDataUpdated(
address indexed reserve,
uint256 liquidityRate,
uint256 stableBorrowRate,
uint256 variableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex
);
event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user);
event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user);
event Supply(
address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, uint16 indexed referralCode
);
event SwapBorrowRateMode(address indexed reserve, address indexed user, uint8 interestRateMode);
event UserEModeSet(address indexed user, uint8 categoryId);
event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount);
struct EModeCategory {
uint16 ltv;
uint16 liquidationThreshold;
uint16 liquidationBonus;
address priceSource;
string label;
}
struct ReserveConfigurationMap {
uint256 data;
}
struct ReserveData {
ReserveConfigurationMap configuration;
uint128 liquidityIndex;
uint128 currentLiquidityRate;
uint128 variableBorrowIndex;
uint128 currentVariableBorrowRate;
uint128 currentStableBorrowRate;
uint40 lastUpdateTimestamp;
uint16 id;
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
address interestRateStrategyAddress;
uint128 accruedToTreasury;
uint128 unbacked;
uint128 isolationModeTotalDebt;
}
struct UserConfigurationMap {
uint256 data;
}
function ADDRESSES_PROVIDER() external view returns (address);
function BRIDGE_PROTOCOL_FEE() external view returns (uint256);
function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128);
function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint128);
function MAX_NUMBER_RESERVES() external view returns (uint16);
function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() external view returns (uint256);
function POOL_REVISION() external view returns (uint256);
function backUnbacked(address asset, uint256 amount, uint256 fee) external returns (uint256);
function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)
external;
function configureEModeCategory(uint8 id, DataTypes.EModeCategory memory category) external;
function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
function dropReserve(address asset) external;
function finalizeTransfer(
address asset,
address from,
address to,
uint256 amount,
uint256 balanceFromBefore,
uint256 balanceToBefore
) external;
function flashLoan(
address receiverAddress,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory interestRateModes,
address onBehalfOf,
bytes memory params,
uint16 referralCode
) external;
function flashLoanSimple(
address receiverAddress,
address asset,
uint256 amount,
bytes memory params,
uint16 referralCode
) external;
function getConfiguration(address asset) external view returns (DataTypes.ReserveConfigurationMap memory);
function getEModeCategoryData(uint8 id) external view returns (DataTypes.EModeCategory memory);
function getReserveAddressById(uint16 id) external view returns (address);
function getReserveData(address asset) external view returns (DataTypes.ReserveData memory);
function getReserveNormalizedIncome(address asset) external view returns (uint256);
function getReserveNormalizedVariableDebt(address asset) external view returns (uint256);
function getReservesList() external view returns (address[] memory);
function getUserAccountData(address user)
external
view
returns (
uint256 totalCollateralBase,
uint256 totalDebtBase,
uint256 availableBorrowsBase,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
function getUserConfiguration(address user) external view returns (DataTypes.UserConfigurationMap memory);
function getUserEMode(address user) external view returns (uint256);
function initReserve(
address asset,
address aTokenAddress,
address stableDebtAddress,
address variableDebtAddress,
address interestRateStrategyAddress
) external;
function initialize(address provider) external;
function liquidationCall(
address collateralAsset,
address debtAsset,
address user,
uint256 debtToCover,
bool receiveAToken
) external;
function mintToTreasury(address[] memory assets) external;
function mintUnbacked(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
function rebalanceStableBorrowRate(address asset, address user) external;
function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf)
external
returns (uint256);
function repayWithATokens(address asset, uint256 amount, uint256 interestRateMode) external returns (uint256);
function repayWithPermit(
address asset,
uint256 amount,
uint256 interestRateMode,
address onBehalfOf,
uint256 deadline,
uint8 permitV,
bytes32 permitR,
bytes32 permitS
) external returns (uint256);
function rescueTokens(address token, address to, uint256 amount) external;
function resetIsolationModeTotalDebt(address asset) external;
function setConfiguration(address asset, DataTypes.ReserveConfigurationMap memory configuration) external;
function setReserveInterestRateStrategyAddress(address asset, address rateStrategyAddress) external;
function setUserEMode(uint8 categoryId) external;
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;
function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
function supplyWithPermit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode,
uint256 deadline,
uint8 permitV,
bytes32 permitR,
bytes32 permitS
) external;
function swapBorrowRateMode(address asset, uint256 interestRateMode) external;
function updateBridgeProtocolFee(uint256 protocolFee) external;
function updateFlashloanPremiums(uint128 flashLoanPremiumTotal, uint128 flashLoanPremiumToProtocol) external;
function withdraw(address asset, uint256 amount, address to) external returns (uint256);
}
IPoolAddressesProvider.sol 45 lines
pragma solidity 0.6.10;
interface IPoolAddressesProvider {
event ACLAdminUpdated(address indexed oldAddress, address indexed newAddress);
event ACLManagerUpdated(address indexed oldAddress, address indexed newAddress);
event AddressSet(bytes32 indexed id, address indexed oldAddress, address indexed newAddress);
event AddressSetAsProxy(
bytes32 indexed id,
address indexed proxyAddress,
address oldImplementationAddress,
address indexed newImplementationAddress
);
event MarketIdSet(string indexed oldMarketId, string indexed newMarketId);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event PoolConfiguratorUpdated(address indexed oldAddress, address indexed newAddress);
event PoolDataProviderUpdated(address indexed oldAddress, address indexed newAddress);
event PoolUpdated(address indexed oldAddress, address indexed newAddress);
event PriceOracleSentinelUpdated(address indexed oldAddress, address indexed newAddress);
event PriceOracleUpdated(address indexed oldAddress, address indexed newAddress);
event ProxyCreated(bytes32 indexed id, address indexed proxyAddress, address indexed implementationAddress);
function getACLAdmin() external view returns (address);
function getACLManager() external view returns (address);
function getAddress(bytes32 id) external view returns (address);
function getMarketId() external view returns (string memory);
function getPool() external view returns (address);
function getPoolConfigurator() external view returns (address);
function getPoolDataProvider() external view returns (address);
function getPriceOracle() external view returns (address);
function getPriceOracleSentinel() external view returns (address);
function owner() external view returns (address);
function renounceOwnership() external;
function setACLAdmin(address newAclAdmin) external;
function setACLManager(address newAclManager) external;
function setAddress(bytes32 id, address newAddress) external;
function setAddressAsProxy(bytes32 id, address newImplementationAddress) external;
function setMarketId(string memory newMarketId) external;
function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external;
function setPoolDataProvider(address newDataProvider) external;
function setPoolImpl(address newPoolImpl) external;
function setPriceOracle(address newPriceOracle) external;
function setPriceOracleSentinel(address newPriceOracleSentinel) external;
function transferOwnership(address newOwner) external;
}
IPriceOracle.sol 32 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
/**
* @title IPriceOracle
* @author Set Protocol
*
* Interface for interacting with PriceOracle
*/
interface IPriceOracle {
/* ============ Functions ============ */
function getPrice(address _assetOne, address _assetTwo) external view returns (uint256);
function masterQuoteAsset() external view returns (address);
}
IProtocolDataProvider.sol 18 lines
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
interface IProtocolDataProvider {
struct TokenData {
string symbol;
address tokenAddress;
}
function ADDRESSES_PROVIDER() external view returns (address);
function getAllReservesTokens() external view returns (TokenData[] memory);
function getAllATokens() external view returns (TokenData[] memory);
function getReserveConfigurationData(address asset) external view returns (uint256 decimals, uint256 ltv, uint256 liquidationThreshold, uint256 liquidationBonus, uint256 reserveFactor, bool usageAsCollateralEnabled, bool borrowingEnabled, bool stableBorrowRateEnabled, bool isActive, bool isFrozen);
function getReserveData(address asset) external view returns (uint256 availableLiquidity, uint256 totalStableDebt, uint256 totalVariableDebt, uint256 liquidityRate, uint256 variableBorrowRate, uint256 stableBorrowRate, uint256 averageStableBorrowRate, uint256 liquidityIndex, uint256 variableBorrowIndex, uint40 lastUpdateTimestamp);
function getUserReserveData(address asset, address user) external view returns (uint256 currentATokenBalance, uint256 currentStableDebt, uint256 currentVariableDebt, uint256 principalStableDebt, uint256 scaledVariableDebt, uint256 stableBorrowRate, uint256 liquidityRate, uint40 stableRateLastUpdated, bool usageAsCollateralEnabled);
function getReserveTokensAddresses(address asset) external view returns (address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress);
}
IPrt.sol 8 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IPrt is IERC20 {
function setToken() external view returns (address);
}
IPrtStakingPool.sol 9 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity ^0.6.10;
interface IPrtStakingPool {
function accrue(uint256 _amount) external;
function distributor() external view returns (address);
function stakeToken() external view returns (address);
function rewardToken() external view returns (address);
}
IQuoter.sol 51 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
/// @title Quoter Interface
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IQuoter {
/// @notice Returns the amount out received for a given exact input swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool fee
/// @param amountIn The amount of the first token to swap
/// @return amountOut The amount of the last token that would be received
function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut);
/// @notice Returns the amount out received for a given exact input but for a swap of a single pool
/// @param tokenIn The token being swapped in
/// @param tokenOut The token being swapped out
/// @param fee The fee of the token pool to consider for the pair
/// @param amountIn The desired input amount
/// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountOut The amount of `tokenOut` that would be received
function quoteExactInputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountIn,
uint160 sqrtPriceLimitX96
) external returns (uint256 amountOut);
/// @notice Returns the amount in required for a given exact output swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
/// @param amountOut The amount of the last token to receive
/// @return amountIn The amount of first token required to be paid
function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn);
/// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
/// @param tokenIn The token being swapped in
/// @param tokenOut The token being swapped out
/// @param fee The fee of the token pool to consider for the pair
/// @param amountOut The desired output amount
/// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
function quoteExactOutputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountOut,
uint160 sqrtPriceLimitX96
) external returns (uint256 amountIn);
}
ISetToken.sol 120 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title ISetToken
* @author Set Protocol
*
* Interface for operating with SetTokens.
*/
interface ISetToken is IERC20 {
/* ============ Enums ============ */
enum ModuleState {
NONE,
PENDING,
INITIALIZED
}
/* ============ Structs ============ */
/**
* The base definition of a SetToken Position
*
* @param component Address of token in the Position
* @param module If not in default state, the address of associated module
* @param unit Each unit is the # of components per 10^18 of a SetToken
* @param positionState Position ENUM. Default is 0; External is 1
* @param data Arbitrary data
*/
struct Position {
address component;
address module;
int256 unit;
uint8 positionState;
bytes data;
}
/**
* A struct that stores a component's cash position details and external positions
* This data structure allows O(1) access to a component's cash position units and
* virtual units.
*
* @param virtualUnit Virtual value of a component's DEFAULT position. Stored as virtual for efficiency
* updating all units at once via the position multiplier. Virtual units are achieved
* by dividing a "real" value by the "positionMultiplier"
* @param componentIndex
* @param externalPositionModules List of external modules attached to each external position. Each module
* maps to an external position
* @param externalPositions Mapping of module => ExternalPosition struct for a given component
*/
struct ComponentPosition {
int256 virtualUnit;
address[] externalPositionModules;
mapping(address => ExternalPosition) externalPositions;
}
/**
* A struct that stores a component's external position details including virtual unit and any
* auxiliary data.
*
* @param virtualUnit Virtual value of a component's EXTERNAL position.
* @param data Arbitrary data
*/
struct ExternalPosition {
int256 virtualUnit;
bytes data;
}
/* ============ Functions ============ */
function controller() external view returns (address);
function addComponent(address _component) external;
function removeComponent(address _component) external;
function editDefaultPositionUnit(address _component, int256 _realUnit) external;
function addExternalPositionModule(address _component, address _positionModule) external;
function removeExternalPositionModule(address _component, address _positionModule) external;
function editExternalPositionUnit(address _component, address _positionModule, int256 _realUnit) external;
function editExternalPositionData(address _component, address _positionModule, bytes calldata _data) external;
function invoke(address _target, uint256 _value, bytes calldata _data) external returns(bytes memory);
function editPositionMultiplier(int256 _newMultiplier) external;
function mint(address _account, uint256 _quantity) external;
function burn(address _account, uint256 _quantity) external;
function lock() external;
function unlock() external;
function addModule(address _module) external;
function removeModule(address _module) external;
function initializeModule() external;
function setManager(address _manager) external;
function manager() external view returns (address);
function moduleStates(address _module) external view returns (ModuleState);
function getModules() external view returns (address[] memory);
function getDefaultPositionRealUnit(address _component) external view returns(int256);
function getExternalPositionRealUnit(address _component, address _positionModule) external view returns(int256);
function getComponents() external view returns(address[] memory);
function getExternalPositionModules(address _component) external view returns(address[] memory);
function getExternalPositionData(address _component, address _positionModule) external view returns(bytes memory);
function isExternalPositionModule(address _component, address _module) external view returns(bool);
function isComponent(address _component) external view returns(bool);
function positionMultiplier() external view returns (int256);
function getPositions() external view returns (Position[] memory);
function getTotalComponentRealUnits(address _component) external view returns(int256);
function isInitializedModule(address _module) external view returns(bool);
function isPendingModule(address _module) external view returns(bool);
function isLocked() external view returns (bool);
}
ISetTokenCreator.sol 32 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
interface ISetTokenCreator {
function create(
address[] memory _components,
int256[] memory _units,
address[] memory _modules,
address _manager,
string memory _name,
string memory _symbol
)
external
returns (address);
}
ISetValuer.sol 25 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "../interfaces/ISetToken.sol";
interface ISetValuer {
function calculateSetTokenValuation(ISetToken _setToken, address _quoteAsset) external view returns (uint256);
function calculateComponentValuation(ISetToken _setToken, address _component, address _quoteAsset) external view returns (uint256);
}
ISlippageIssuanceModule.sol 49 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity >=0.6.10;
import { IDebtIssuanceModule, ISetToken } from "./IDebtIssuanceModule.sol";
interface ISlippageIssuanceModule is IDebtIssuanceModule {
function issueWithSlippage(
ISetToken _setToken,
uint256 _setQuantity,
address[] memory _checkedComponents,
uint256[] memory _maxTokenAmountsIn,
address _to
) external;
function redeemWithSlippage(
ISetToken _setToken,
uint256 _setQuantity,
address[] memory _checkedComponents,
uint256[] memory _minTokenAmountsOut,
address _to
) external;
function getRequiredComponentIssuanceUnitsOffChain(
ISetToken _setToken,
uint256 _quantity
)
external
returns (address[] memory, uint256[] memory, uint256[] memory);
function getRequiredComponentRedemptionUnitsOffChain(
ISetToken _setToken,
uint256 _quantity
)
external
returns (address[] memory, uint256[] memory, uint256[] memory);
}
IStreamingFeeModule.sol 20 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "./ISetToken.sol";
interface IStreamingFeeModule {
struct FeeState {
address feeRecipient;
uint256 maxStreamingFeePercentage;
uint256 streamingFeePercentage;
uint256 lastStreamingFeeTimestamp;
}
function getFee(ISetToken _setToken) external view returns (uint256);
function accrueFee(ISetToken _setToken) external;
function updateStreamingFee(ISetToken _setToken, uint256 _newFee) external;
function updateFeeRecipient(ISetToken _setToken, address _newFeeRecipient) external;
function initialize(ISetToken _setToken, FeeState memory _settings) external;
}
ITradeModule.sol 33 lines
/*
Copyright 2022 Index Cooperative.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.6.10;
import { ISetToken } from "../interfaces/ISetToken.sol";
interface ITradeModule {
function initialize(ISetToken _setToken) external;
function trade(
ISetToken _setToken,
string memory _exchangeName,
address _sendToken,
uint256 _sendQuantity,
address _receiveToken,
uint256 _minReceiveQuantity,
bytes memory _data
) external;
}
IUniswapV2Router.sol 113 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
interface IUniswapV2Router {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}
IUniswapV3SwapCallback.sol 22 lines
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
IWETH.sol 8 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity >=0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint) external;
}
IWrapModule.sol 56 lines
/*
Copyright 2021 Index Coop.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
import { ISetToken } from "./ISetToken.sol";
pragma solidity 0.6.10;
interface IWrapModule {
function initialize(ISetToken _setToken) external;
function wrap(
ISetToken _setToken,
address _underlyingToken,
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName
) external;
function wrapWithEther(
ISetToken _setToken,
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName
) external;
function unwrap(
ISetToken _setToken,
address _underlyingToken,
address _wrappedToken,
uint256 _wrappedUnits,
string calldata _integrationName
) external;
function unwrapWithEther(
ISetToken _setToken,
address _wrappedToken,
uint256 _wrappedUnits,
string calldata _integrationName
) external;
}
IWrapModuleV2.sol 63 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ISetToken } from "./ISetToken.sol";
import { IWETH } from "./IWETH.sol";
interface IWrapModuleV2 {
function weth() external view returns(IWETH);
function initialize(ISetToken _setToken) external;
function wrap(
ISetToken _setToken,
address _underlyingToken,
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName,
bytes memory _wrapData
) external;
function wrapWithEther(
ISetToken _setToken,
address _wrappedToken,
uint256 _underlyingUnits,
string calldata _integrationName,
bytes memory _wrapData
) external;
function unwrap(
ISetToken _setToken,
address _underlyingToken,
address _wrappedToken,
uint256 _wrappedUnits,
string calldata _integrationName,
bytes memory _unwrapData
) external;
function unwrapWithEther(
ISetToken _setToken,
address _wrappedToken,
uint256 _wrappedUnits,
string calldata _integrationName,
bytes memory _unwrapData
) external;
}
IWrappedfCash.sol 75 lines
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @notice Different types of internal tokens
/// - UnderlyingToken: underlying asset for a cToken (except for Ether)
/// - cToken: Compound interest bearing token
/// - cETH: Special handling for cETH tokens
/// - Ether: the one and only
/// - NonMintable: tokens that do not have an underlying (therefore not cTokens)
enum TokenType {
UnderlyingToken,
cToken,
cETH,
Ether,
NonMintable
}
interface IWrappedfCash {
function initialize(uint16 currencyId, uint40 maturity) external;
/// @notice Mints wrapped fCash ERC20 tokens
function mintViaAsset(
uint256 depositAmountExternal,
uint88 fCashAmount,
address receiver,
uint32 minImpliedRate
) external;
function mintViaUnderlying(
uint256 depositAmountExternal,
uint88 fCashAmount,
address receiver,
uint32 minImpliedRate
) external;
function redeemToAsset(uint256 amount, address receiver, uint32 maxImpliedRate) external;
function redeemToUnderlying(uint256 amount, address receiver, uint32 maxImpliedRate) external;
/// @notice Returns the underlying fCash ID of the token
function getfCashId() external view returns (uint256);
/// @notice True if the fCash has matured, assets mature exactly on the block time
function hasMatured() external view returns (bool);
/// @notice Returns the components of the fCash idd
function getDecodedID() external view returns (uint16 currencyId, uint40 maturity);
/// @notice Returns the current market index for this fCash asset. If this returns
/// zero that means it is idiosyncratic and cannot be traded.
function getMarketIndex() external view returns (uint8);
/// @notice Returns the token and precision of the token that this token settles
/// to. For example, fUSDC will return the USDC token address and 1e6. The zero
/// address will represent ETH.
function getUnderlyingToken() external view returns (IERC20 underlyingToken, int256 underlyingPrecision);
/// @notice Returns the asset token which the fCash settles to. This will be an interest
/// bearing token like a cToken or aToken.
function getAssetToken() external view returns (IERC20 assetToken, int256 assetPrecision, TokenType tokenType);
function getToken(bool useUnderlying) external view returns (IERC20 token, bool isETH);
function previewMint(uint256 shares) external view returns (uint256);
function previewRedeem(uint256 shares) external view returns (uint256);
}
interface IWrappedfCashComplete is IWrappedfCash, IERC20 {
function getMaturity() external view returns (uint40);
}
IWrappedfCashFactory.sol 7 lines
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.6.10;
interface IWrappedfCashFactory {
function deployWrapper(uint16 currencyId, uint40 maturity) external returns(address);
function computeAddress(uint16 currencyId, uint40 maturity) external view returns(address);
}
IWrapV2Adapter.sol 61 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
/**
* @title IWrapV2Adapter
* @author Set Protocol
*/
interface IWrapV2Adapter {
function ETH_TOKEN_ADDRESS() external view returns (address);
function getWrapCallData(
address _underlyingToken,
address _wrappedToken,
uint256 _underlyingUnits,
address _to,
bytes memory _wrapData
)
external
view
returns (
address _subject,
uint256 _value,
bytes memory _calldata
);
function getUnwrapCallData(
address _underlyingToken,
address _wrappedToken,
uint256 _wrappedTokenUnits,
address _to,
bytes memory _unwrapData
)
external
view
returns (
address _subject,
uint256 _value,
bytes memory _calldata
);
function getSpenderAddress(address _underlyingToken, address _wrappedToken)
external
view
returns (address);
}
OptimisticOracleV3Interface.sol 179 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title Optimistic Oracle V3 Interface that callers must use to assert truths about the world.
*/
interface OptimisticOracleV3Interface {
// Struct grouping together the settings related to the escalation manager stored in the assertion.
struct EscalationManagerSettings {
bool arbitrateViaEscalationManager; // False if the DVM is used as an oracle (EscalationManager on True).
bool discardOracle; // False if Oracle result is used for resolving assertion after dispute.
bool validateDisputers; // True if the EM isDisputeAllowed should be checked on disputes.
address assertingCaller; // Stores msg.sender when assertion was made.
address escalationManager; // Address of the escalation manager (zero address if not configured).
}
// Struct for storing properties and lifecycle of an assertion.
struct Assertion {
EscalationManagerSettings escalationManagerSettings; // Settings related to the escalation manager.
address asserter; // Address of the asserter.
uint64 assertionTime; // Time of the assertion.
bool settled; // True if the request is settled.
IERC20 currency; // ERC20 token used to pay rewards and fees.
uint64 expirationTime; // Unix timestamp marking threshold when the assertion can no longer be disputed.
bool settlementResolution; // Resolution of the assertion (false till resolved).
bytes32 domainId; // Optional domain that can be used to relate the assertion to others in the escalationManager.
bytes32 identifier; // UMA DVM identifier to use for price requests in the event of a dispute.
uint256 bond; // Amount of currency that the asserter has bonded.
address callbackRecipient; // Address that receives the callback.
address disputer; // Address of the disputer.
}
// Struct for storing cached currency whitelist.
struct WhitelistedCurrency {
bool isWhitelisted; // True if the currency is whitelisted.
uint256 finalFee; // Final fee of the currency.
}
/**
* @notice Disputes an assertion. Depending on how the assertion was configured, this may either escalate to the UMA
* DVM or the configured escalation manager for arbitration.
* @dev The caller must approve this contract to spend at least bond amount of currency for the associated assertion.
* @param assertionId unique identifier for the assertion to dispute.
* @param disputer receives bonds back at settlement.
*/
function disputeAssertion(bytes32 assertionId, address disputer) external;
/**
* @notice Returns the default identifier used by the Optimistic Oracle V3.
* @return The default identifier.
*/
function defaultIdentifier() external view returns (bytes32);
/**
* @notice Fetches information about a specific assertion and returns it.
* @param assertionId unique identifier for the assertion to fetch information for.
* @return assertion information about the assertion.
*/
function getAssertion(bytes32 assertionId) external view returns (Assertion memory);
/**
* @notice Asserts a truth about the world, using the default currency and liveness. No callback recipient or
* escalation manager is enabled. The caller is expected to provide a bond of finalFee/burnedBondPercentage
* (with burnedBondPercentage set to 50%, the bond is 2x final fee) of the default currency.
* @dev The caller must approve this contract to spend at least the result of getMinimumBond(defaultCurrency).
* @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers.
* @param asserter receives bonds back at settlement. This could be msg.sender or
* any other account that the caller wants to receive the bond at settlement time.
* @return assertionId unique identifier for this assertion.
*/
function assertTruthWithDefaults(bytes memory claim, address asserter) external returns (bytes32);
/**
* @notice Asserts a truth about the world, using a fully custom configuration.
* @dev The caller must approve this contract to spend at least bond amount of currency.
* @param claim the truth claim being asserted. This is an assertion about the world, and is verified by disputers.
* @param asserter receives bonds back at settlement. This could be msg.sender or
* any other account that the caller wants to receive the bond at settlement time.
* @param callbackRecipient if configured, this address will receive a function call assertionResolvedCallback and
* assertionDisputedCallback at resolution or dispute respectively. Enables dynamic responses to these events. The
* recipient _must_ implement these callbacks and not revert or the assertion resolution will be blocked.
* @param escalationManager if configured, this address will control escalation properties of the assertion. This
* means a) choosing to arbitrate via the UMA DVM, b) choosing to discard assertions on dispute, or choosing to
* validate disputes. Combining these, the asserter can define their own security properties for the assertion.
* escalationManager also _must_ implement the same callbacks as callbackRecipient.
* @param liveness time to wait before the assertion can be resolved. Assertion can be disputed in this time.
* @param currency bond currency pulled from the caller and held in escrow until the assertion is resolved.
* @param bond amount of currency to pull from the caller and hold in escrow until the assertion is resolved. This
* must be >= getMinimumBond(address(currency)).
* @param identifier UMA DVM identifier to use for price requests in the event of a dispute. Must be pre-approved.
* @param domainId optional domain that can be used to relate this assertion to others in the escalationManager and
* can be used by the configured escalationManager to define custom behavior for groups of assertions. This is
* typically used for "escalation games" by changing bonds or other assertion properties based on the other
* assertions that have come before. If not needed this value should be 0 to save gas.
* @return assertionId unique identifier for this assertion.
*/
function assertTruth(
bytes memory claim,
address asserter,
address callbackRecipient,
address escalationManager,
uint64 liveness,
IERC20 currency,
uint256 bond,
bytes32 identifier,
bytes32 domainId
) external returns (bytes32);
/**
* @notice Fetches information about a specific identifier & currency from the UMA contracts and stores a local copy
* of the information within this contract. This is used to save gas when making assertions as we can avoid an
* external call to the UMA contracts to fetch this.
* @param identifier identifier to fetch information for and store locally.
* @param currency currency to fetch information for and store locally.
*/
function syncUmaParams(bytes32 identifier, address currency) external;
/**
* @notice Resolves an assertion. If the assertion has not been disputed, the assertion is resolved as true and the
* asserter receives the bond. If the assertion has been disputed, the assertion is resolved depending on the oracle
* result. Based on the result, the asserter or disputer receives the bond. If the assertion was disputed then an
* amount of the bond is sent to the UMA Store as an oracle fee based on the burnedBondPercentage. The remainder of
* the bond is returned to the asserter or disputer.
* @param assertionId unique identifier for the assertion to resolve.
*/
function settleAssertion(bytes32 assertionId) external;
/**
* @notice Settles an assertion and returns the resolution.
* @param assertionId unique identifier for the assertion to resolve and return the resolution for.
* @return resolution of the assertion.
*/
function settleAndGetAssertionResult(bytes32 assertionId) external returns (bool);
/**
* @notice Fetches the resolution of a specific assertion and returns it. If the assertion has not been settled then
* this will revert. If the assertion was disputed and configured to discard the oracle resolution return false.
* @param assertionId unique identifier for the assertion to fetch the resolution for.
* @return resolution of the assertion.
*/
function getAssertionResult(bytes32 assertionId) external view returns (bool);
/**
* @notice Returns the minimum bond amount required to make an assertion. This is calculated as the final fee of the
* currency divided by the burnedBondPercentage. If burn percentage is 50% then the min bond is 2x the final fee.
* @param currency currency to calculate the minimum bond for.
* @return minimum bond amount.
*/
function getMinimumBond(address currency) external view returns (uint256);
event AssertionMade(
bytes32 indexed assertionId,
bytes32 domainId,
bytes claim,
address indexed asserter,
address callbackRecipient,
address escalationManager,
address caller,
uint64 expirationTime,
IERC20 currency,
uint256 bond,
bytes32 indexed identifier
);
event AssertionDisputed(bytes32 indexed assertionId, address indexed caller, address indexed disputer);
event AssertionSettled(
bytes32 indexed assertionId,
address indexed bondRecipient,
bool disputed,
bool settlementResolution,
address settleCaller
);
event AdminPropertiesSet(IERC20 defaultCurrency, uint64 defaultLiveness, uint256 burnedBondPercentage);
}
FliRebalanceKeeper.sol 112 lines
/*
Copyright 2022 Index Cooperative.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { KeeperCompatibleInterface } from "@chainlink/contracts/src/v0.6/KeeperCompatible.sol";
import { IFlexibleLeverageStrategyExtension } from "../interfaces/IFlexibleLeverageStrategyExtension.sol";
/**
* @title RebalanceKeeper
* @author Index Cooperative
*
* Chainlink Keeper which automatically rebalances FLI SetTokens.
*/
contract FliRebalanceKeeper is Ownable, KeeperCompatibleInterface {
using Address for address;
/* ============ Structs ============ */
struct LeverageSettings {
uint256 customMinLeverageRatio; // The minimum leverage ratio
uint256 customMaxLeverageRatio; // The maximum leverage ratio
}
/* ============ Modifiers ============ */
modifier onlyRegistry() {
require(msg.sender == registryAddress, "Only registry address can call this function");
_;
}
/* ============ State Variables ============ */
IFlexibleLeverageStrategyExtension public fliExtension; // Address of the fli extension contract
address public registryAddress; // Address of the chainlink keeper registry
uint256 public exchangeIndex; // The index of the exchange to use
LeverageSettings public leverageSettings; // The leverage settings to check whether should rebalance
/* ============ Constructor ============ */
constructor(
IFlexibleLeverageStrategyExtension _fliExtension,
address _registryAddress,
uint256 _exchangeIndex,
LeverageSettings memory _leverageSettings
) public {
fliExtension = _fliExtension;
registryAddress = _registryAddress;
exchangeIndex = _exchangeIndex;
leverageSettings = _leverageSettings;
}
/**
* As checkUpkeep is not a view function, calling this function will actually consume gas.
* As such if a keeper calls this function, it will always return true so that performUpkeep will be called.
*/
function checkUpkeep(bytes calldata /* checkData */) external override returns (bool, bytes memory) {
(string[] memory exchangeNames, IFlexibleLeverageStrategyExtension.ShouldRebalance[] memory shouldRebalances) = fliExtension.shouldRebalanceWithBounds(
leverageSettings.customMinLeverageRatio,
leverageSettings.customMaxLeverageRatio
);
IFlexibleLeverageStrategyExtension.ShouldRebalance shouldRebalance = shouldRebalances[exchangeIndex];
bytes memory performData = abi.encode(shouldRebalance, exchangeNames[exchangeIndex]);
return (shouldRebalance != IFlexibleLeverageStrategyExtension.ShouldRebalance.NONE, performData);
}
/**
* performUpkeep checks that a rebalance is required. Otherwise the contract call will revert.
*/
function performUpkeep(bytes calldata performData) external override onlyRegistry {
require(performData.length > 0, "Invalid performData");
(IFlexibleLeverageStrategyExtension.ShouldRebalance shouldRebalance, string memory exchangeName) = abi.decode(
performData,
(IFlexibleLeverageStrategyExtension.ShouldRebalance, string)
);
if (shouldRebalance == IFlexibleLeverageStrategyExtension.ShouldRebalance.REBALANCE) {
fliExtension.rebalance(exchangeName);
return;
} else if (shouldRebalance == IFlexibleLeverageStrategyExtension.ShouldRebalance.ITERATE_REBALANCE) {
fliExtension.iterateRebalance(exchangeName);
return;
} else if (shouldRebalance == IFlexibleLeverageStrategyExtension.ShouldRebalance.RIPCORD) {
fliExtension.ripcord(exchangeName);
return;
}
revert("FliRebalanceKeeper: invalid shouldRebalance or no rebalance required");
}
function setExchangeIndex(uint256 _exchangeIndex) external onlyOwner {
exchangeIndex = _exchangeIndex;
}
function setLeverageSettings(LeverageSettings memory _leverageSettings) external onlyOwner {
leverageSettings = _leverageSettings;
}
}
AddressArrayUtils.sol 225 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
/**
* @title AddressArrayUtils
* @author Set Protocol
*
* Utility functions to handle Address Arrays
*
* CHANGELOG:
* - 4/27/21: Added validatePairsWithArray methods
*/
library AddressArrayUtils {
/**
* Finds the index of the first occurrence of the given element.
* @param A The input array to search
* @param a The value to find
* @return Returns (index and isIn) for the first occurrence starting from index 0
*/
function indexOf(address[] memory A, address a) internal pure returns (uint256, bool) {
uint256 length = A.length;
for (uint256 i = 0; i < length; i++) {
if (A[i] == a) {
return (i, true);
}
}
return (uint256(-1), false);
}
/**
* Returns true if the value is present in the list. Uses indexOf internally.
* @param A The input array to search
* @param a The value to find
* @return Returns isIn for the first occurrence starting from index 0
*/
function contains(address[] memory A, address a) internal pure returns (bool) {
(, bool isIn) = indexOf(A, a);
return isIn;
}
/**
* Returns true if there are 2 elements that are the same in an array
* @param A The input array to search
* @return Returns boolean for the first occurrence of a duplicate
*/
function hasDuplicate(address[] memory A) internal pure returns(bool) {
require(A.length > 0, "A is empty");
for (uint256 i = 0; i < A.length - 1; i++) {
address current = A[i];
for (uint256 j = i + 1; j < A.length; j++) {
if (current == A[j]) {
return true;
}
}
}
return false;
}
/**
* @param A The input array to search
* @param a The address to remove
* @return Returns the array with the object removed.
*/
function remove(address[] memory A, address a)
internal
pure
returns (address[] memory)
{
(uint256 index, bool isIn) = indexOf(A, a);
if (!isIn) {
revert("Address not in array.");
} else {
(address[] memory _A,) = pop(A, index);
return _A;
}
}
/**
* @param A The input array to search
* @param a The address to remove
*/
function removeStorage(address[] storage A, address a)
internal
{
(uint256 index, bool isIn) = indexOf(A, a);
if (!isIn) {
revert("Address not in array.");
} else {
uint256 lastIndex = A.length - 1; // If the array would be empty, the previous line would throw, so no underflow here
if (index != lastIndex) { A[index] = A[lastIndex]; }
A.pop();
}
}
/**
* Removes specified index from array
* @param A The input array to search
* @param index The index to remove
* @return Returns the new array and the removed entry
*/
function pop(address[] memory A, uint256 index)
internal
pure
returns (address[] memory, address)
{
uint256 length = A.length;
require(index < A.length, "Index must be < A length");
address[] memory newAddresses = new address[](length - 1);
for (uint256 i = 0; i < index; i++) {
newAddresses[i] = A[i];
}
for (uint256 j = index + 1; j < length; j++) {
newAddresses[j - 1] = A[j];
}
return (newAddresses, A[index]);
}
/**
* Returns the combination of the two arrays
* @param A The first array
* @param B The second array
* @return Returns A extended by B
*/
function extend(address[] memory A, address[] memory B) internal pure returns (address[] memory) {
uint256 aLength = A.length;
uint256 bLength = B.length;
address[] memory newAddresses = new address[](aLength + bLength);
for (uint256 i = 0; i < aLength; i++) {
newAddresses[i] = A[i];
}
for (uint256 j = 0; j < bLength; j++) {
newAddresses[aLength + j] = B[j];
}
return newAddresses;
}
/**
* Validate that address and uint array lengths match. Validate address array is not empty
* and contains no duplicate elements.
*
* @param A Array of addresses
* @param B Array of uint
*/
function validatePairsWithArray(address[] memory A, uint[] memory B) internal pure {
require(A.length == B.length, "Array length mismatch");
_validateLengthAndUniqueness(A);
}
/**
* Validate that address and bool array lengths match. Validate address array is not empty
* and contains no duplicate elements.
*
* @param A Array of addresses
* @param B Array of bool
*/
function validatePairsWithArray(address[] memory A, bool[] memory B) internal pure {
require(A.length == B.length, "Array length mismatch");
_validateLengthAndUniqueness(A);
}
/**
* Validate that address and string array lengths match. Validate address array is not empty
* and contains no duplicate elements.
*
* @param A Array of addresses
* @param B Array of strings
*/
function validatePairsWithArray(address[] memory A, string[] memory B) internal pure {
require(A.length == B.length, "Array length mismatch");
_validateLengthAndUniqueness(A);
}
/**
* Validate that address array lengths match, and calling address array are not empty
* and contain no duplicate elements.
*
* @param A Array of addresses
* @param B Array of addresses
*/
function validatePairsWithArray(address[] memory A, address[] memory B) internal pure {
require(A.length == B.length, "Array length mismatch");
_validateLengthAndUniqueness(A);
}
/**
* Validate that address and bytes array lengths match. Validate address array is not empty
* and contains no duplicate elements.
*
* @param A Array of addresses
* @param B Array of bytes
*/
function validatePairsWithArray(address[] memory A, bytes[] memory B) internal pure {
require(A.length == B.length, "Array length mismatch");
_validateLengthAndUniqueness(A);
}
/**
* Validate address array is not empty and contains no duplicate elements.
*
* @param A Array of addresses
*/
function _validateLengthAndUniqueness(address[] memory A) internal pure {
require(A.length > 0, "Array length must be > 0");
require(!hasDuplicate(A), "Cannot duplicate addresses");
}
}
AncillaryData.sol 142 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.6.10;
/**
* @title Library for encoding and decoding ancillary data for DVM price requests.
* @notice We assume that on-chain ancillary data can be formatted directly from bytes to utf8 encoding via
* web3.utils.hexToUtf8, and that clients will parse the utf8-encoded ancillary data as a comma-delimitted key-value
* dictionary. Therefore, this library provides internal methods that aid appending to ancillary data from Solidity
* smart contracts. More details on UMA's ancillary data guidelines below:
* https://docs.google.com/document/d/1zhKKjgY1BupBGPPrY_WOJvui0B6DMcd-xDR8-9-SPDw/edit
*/
library AncillaryData {
// This converts the bottom half of a bytes32 input to hex in a highly gas-optimized way.
// Source: the brilliant implementation at https://gitter.im/ethereum/solidity?at=5840d23416207f7b0ed08c9b.
function toUtf8Bytes32Bottom(bytes32 bytesIn) private pure returns (bytes32) {
uint256 x = uint256(bytesIn);
// Nibble interleave
x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
x = (x | (x * 2**8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
x = (x | (x * 2**4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
// Hex encode
uint256 h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
uint256 i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
uint256 j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;
// Return the result.
return bytes32(x);
}
/**
* @notice Returns utf8-encoded bytes32 string that can be read via web3.utils.hexToUtf8.
* @dev Will return bytes32 in all lower case hex characters and without the leading 0x.
* This has minor changes from the toUtf8BytesAddress to control for the size of the input.
* @param bytesIn bytes32 to encode.
* @return utf8 encoded bytes32.
*/
function toUtf8Bytes(bytes32 bytesIn) internal pure returns (bytes memory) {
return abi.encodePacked(toUtf8Bytes32Bottom(bytesIn >> 128), toUtf8Bytes32Bottom(bytesIn));
}
/**
* @notice Returns utf8-encoded address that can be read via web3.utils.hexToUtf8.
* Source: https://ethereum.stackexchange.com/questions/8346/convert-address-to-string/8447#8447
* @dev Will return address in all lower case characters and without the leading 0x.
* @param x address to encode.
* @return utf8 encoded address bytes.
*/
function toUtf8BytesAddress(address x) internal pure returns (bytes memory) {
return
abi.encodePacked(toUtf8Bytes32Bottom(bytes32(bytes20(x)) >> 128), bytes8(toUtf8Bytes32Bottom(bytes20(x))));
}
/**
* @notice Converts a uint into a base-10, UTF-8 representation stored in a `string` type.
* @dev This method is based off of this code: https://stackoverflow.com/a/65707309.
*/
function toUtf8BytesUint(uint256 x) internal pure returns (bytes memory) {
if (x == 0) {
return "0";
}
uint256 j = x;
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint256 k = len;
while (x != 0) {
k = k - 1;
uint8 temp = (48 + uint8(x - (x / 10) * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
x /= 10;
}
return bstr;
}
function appendKeyValueBytes32(
bytes memory currentAncillaryData,
bytes memory key,
bytes32 value
) internal pure returns (bytes memory) {
bytes memory prefix = constructPrefix(currentAncillaryData, key);
return abi.encodePacked(currentAncillaryData, prefix, toUtf8Bytes(value));
}
/**
* @notice Adds "key:value" to `currentAncillaryData` where `value` is an address that first needs to be converted
* to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return
* `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`.
* @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not.
* @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not.
* @param value An address to set as the value in the key:value pair to append to `currentAncillaryData`.
* @return Newly appended ancillary data.
*/
function appendKeyValueAddress(
bytes memory currentAncillaryData,
bytes memory key,
address value
) internal pure returns (bytes memory) {
bytes memory prefix = constructPrefix(currentAncillaryData, key);
return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesAddress(value));
}
/**
* @notice Adds "key:value" to `currentAncillaryData` where `value` is a uint that first needs to be converted
* to utf8 bytes. For example, if `utf8(currentAncillaryData)="k1:v1"`, then this function will return
* `utf8(k1:v1,key:value)`, and if `currentAncillaryData` is blank, then this will return `utf8(key:value)`.
* @param currentAncillaryData This bytes data should ideally be able to be utf8-decoded, but its OK if not.
* @param key Again, this bytes data should ideally be able to be utf8-decoded, but its OK if not.
* @param value A uint to set as the value in the key:value pair to append to `currentAncillaryData`.
* @return Newly appended ancillary data.
*/
function appendKeyValueUint(
bytes memory currentAncillaryData,
bytes memory key,
uint256 value
) internal pure returns (bytes memory) {
bytes memory prefix = constructPrefix(currentAncillaryData, key);
return abi.encodePacked(currentAncillaryData, prefix, toUtf8BytesUint(value));
}
/**
* @notice Helper method that returns the left hand side of a "key:value" pair plus the colon ":" and a leading
* comma "," if the `currentAncillaryData` is not empty. The return value is intended to be prepended as a prefix to
* some utf8 value that is ultimately added to a comma-delimited, key-value dictionary.
*/
function constructPrefix(bytes memory currentAncillaryData, bytes memory key) internal pure returns (bytes memory) {
if (currentAncillaryData.length > 0) {
return abi.encodePacked(",", key, ":");
} else {
return abi.encodePacked(key, ":");
}
}
}
AssetAllowList.sol 147 lines
/*
Copyright 2023 Index Coop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { AddressArrayUtils } from "./AddressArrayUtils.sol";
/**
* @title AssetAllowList
* @author Index Coop
*
* Abstract contract that allows inheriting contracts to restrict the assets that can be traded to, wrapped to, or claimed
*/
abstract contract AssetAllowList {
using AddressArrayUtils for address[];
/* ============ Events ============ */
event AllowedAssetAdded(
address indexed _asset
);
event AllowedAssetRemoved(
address indexed _asset
);
event UseAssetAllowlistUpdated(
bool _status
);
/* ============ State Variables ============ */
// Boolean indicating wether to use asset allow list
bool public useAssetAllowlist;
// Mapping keeping track of allowed assets
mapping(address => bool) public assetAllowlist;
// List of allowed assets
address[] internal allowedAssets;
/* ============ Modifiers ============ */
modifier onlyAllowedAssets(address[] memory _assets) {
require(
_areAllowedAssets(_assets),
"Invalid asset"
);
_;
}
/* ============ Constructor ============ */
/**
* Set state variables and map asset pairs to their oracles
*
* @param _allowedAssets Array of allowed assets
* @param _useAssetAllowlist Bool indicating whether to use asset allow list
*/
constructor(address[] memory _allowedAssets, bool _useAssetAllowlist) public {
_addAllowedAssets(_allowedAssets);
_updateUseAssetAllowlist(_useAssetAllowlist);
}
/* ============ External Functions ============ */
function getAllowedAssets() external view returns(address[] memory) {
return allowedAssets;
}
/* ============ Internal Functions ============ */
/**
* Add new assets that can be traded to, wrapped to, or claimed
*
* @param _assets New asset to add
*/
function _addAllowedAssets(address[] memory _assets) internal {
for (uint256 i = 0; i < _assets.length; i++) {
address asset = _assets[i];
require(!assetAllowlist[asset], "Asset already added");
allowedAssets.push(asset);
assetAllowlist[asset] = true;
emit AllowedAssetAdded(asset);
}
}
/**
* Remove asset(s) so that it/they can't be traded to, wrapped to, or claimed
*
* @param _assets Asset(s) to remove
*/
function _removeAllowedAssets(address[] memory _assets) internal {
for (uint256 i = 0; i < _assets.length; i++) {
address asset = _assets[i];
require(assetAllowlist[asset], "Asset not already added");
allowedAssets.removeStorage(asset);
assetAllowlist[asset] = false;
emit AllowedAssetRemoved(asset);
}
}
/**
* Toggle useAssetAllowlist on and off. When false asset allowlist is ignored
* when true it is enforced.
*
* @param _useAssetAllowlist Bool indicating whether to use asset allow list
*/
function _updateUseAssetAllowlist(bool _useAssetAllowlist) internal {
useAssetAllowlist = _useAssetAllowlist;
emit UseAssetAllowlistUpdated(_useAssetAllowlist);
}
/// @notice Check that all assets in array are allowed to be treated
/// @dev ca be bypassed by setting the useAssetAllowlist to false (default)
function _areAllowedAssets(address[] memory _assets) internal view returns(bool) {
if (!useAssetAllowlist) { return true; }
for (uint256 i = 0; i < _assets.length; i++) {
if (!assetAllowlist[_assets[i]]) { return false; }
}
return true;
}
}
BaseExtension.sol 148 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
/**
* @title BaseExtension
* @author Set Protocol
*
* Abstract class that houses common extension-related state and functions.
*/
abstract contract BaseExtension {
using AddressArrayUtils for address[];
/* ============ Events ============ */
event CallerStatusUpdated(address indexed _caller, bool _status);
event AnyoneCallableUpdated(bool indexed _status);
/* ============ Modifiers ============ */
/**
* Throws if the sender is not the SetToken operator
*/
modifier onlyOperator() {
require(msg.sender == manager.operator(), "Must be operator");
_;
}
/**
* Throws if the sender is not the SetToken methodologist
*/
modifier onlyMethodologist() {
require(msg.sender == manager.methodologist(), "Must be methodologist");
_;
}
/**
* Throws if caller is a contract, can be used to stop flash loan and sandwich attacks
*/
modifier onlyEOA() {
require(msg.sender == tx.origin, "Caller must be EOA Address");
_;
}
/**
* Throws if not allowed caller
*/
modifier onlyAllowedCaller(address _caller) {
require(isAllowedCaller(_caller), "Address not permitted to call");
_;
}
/* ============ State Variables ============ */
// Instance of manager contract
IBaseManager public manager;
// Boolean indicating if anyone can call function
bool public anyoneCallable;
// Mapping of addresses allowed to call function
mapping(address => bool) public callAllowList;
/* ============ Constructor ============ */
constructor(IBaseManager _manager) public { manager = _manager; }
/* ============ External Functions ============ */
/**
* OPERATOR ONLY: Toggle ability for passed addresses to call only allowed caller functions
*
* @param _callers Array of caller addresses to toggle status
* @param _statuses Array of statuses for each caller
*/
function updateCallerStatus(address[] calldata _callers, bool[] calldata _statuses) external onlyOperator {
require(_callers.length == _statuses.length, "Array length mismatch");
require(_callers.length > 0, "Array length must be > 0");
require(!_callers.hasDuplicate(), "Cannot duplicate callers");
for (uint256 i = 0; i < _callers.length; i++) {
address caller = _callers[i];
bool status = _statuses[i];
callAllowList[caller] = status;
emit CallerStatusUpdated(caller, status);
}
}
/**
* OPERATOR ONLY: Toggle whether anyone can call function, bypassing the callAllowlist
*
* @param _status Boolean indicating whether to allow anyone call
*/
function updateAnyoneCallable(bool _status) external onlyOperator {
anyoneCallable = _status;
emit AnyoneCallableUpdated(_status);
}
/* ============ Internal Functions ============ */
/**
* Invoke manager to transfer tokens from manager to other contract.
*
* @param _token Token being transferred from manager contract
* @param _amount Amount of token being transferred
*/
function invokeManagerTransfer(address _token, address _destination, uint256 _amount) internal {
manager.transferTokens(_token, _destination, _amount);
}
/**
* Invoke call from manager
*
* @param _module Module to interact with
* @param _encoded Encoded byte data
*/
function invokeManager(address _module, bytes memory _encoded) internal {
manager.interactManager(_module, _encoded);
}
/**
* Determine if passed address is allowed to call function. If anyoneCallable set to true anyone can call otherwise needs to be approved.
*
* return bool Boolean indicating if allowed caller
*/
function isAllowedCaller(address _caller) internal view virtual returns (bool) {
return anyoneCallable || callAllowList[_caller];
}
}
BaseGlobalExtension.sol 157 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
/**
* @title BaseGlobalExtension
* @author Set Protocol
*
* Abstract class that houses common global extension-related functions. Global extensions must
* also have their own initializeExtension function (not included here because interfaces will vary).
*/
abstract contract BaseGlobalExtension {
using AddressArrayUtils for address[];
/* ============ Events ============ */
event ExtensionRemoved(
address indexed _setToken,
address indexed _delegatedManager
);
/* ============ State Variables ============ */
// Address of the ManagerCore
IManagerCore public immutable managerCore;
// Mapping from Set Token to DelegatedManager
mapping(ISetToken => IDelegatedManager) public setManagers;
/* ============ Modifiers ============ */
/**
* Throws if the sender is not the SetToken manager contract owner
*/
modifier onlyOwner(ISetToken _setToken) {
require(msg.sender == _manager(_setToken).owner(), "Must be owner");
_;
}
/**
* Throws if the sender is not the SetToken methodologist
*/
modifier onlyMethodologist(ISetToken _setToken) {
require(msg.sender == _manager(_setToken).methodologist(), "Must be methodologist");
_;
}
/**
* Throws if the sender is not a SetToken operator
*/
modifier onlyOperator(ISetToken _setToken) {
require(_manager(_setToken).operatorAllowlist(msg.sender), "Must be approved operator");
_;
}
/**
* Throws if the sender is not the SetToken manager contract owner or if the manager is not enabled on the ManagerCore
*/
modifier onlyOwnerAndValidManager(IDelegatedManager _delegatedManager) {
require(msg.sender == _delegatedManager.owner(), "Must be owner");
require(managerCore.isManager(address(_delegatedManager)), "Must be ManagerCore-enabled manager");
_;
}
/**
* Throws if asset is not allowed to be held by the Set
*/
modifier onlyAllowedAsset(ISetToken _setToken, address _asset) {
require(_manager(_setToken).isAllowedAsset(_asset), "Must be allowed asset");
_;
}
/* ============ Constructor ============ */
/**
* Set state variables
*
* @param _managerCore Address of managerCore contract
*/
constructor(IManagerCore _managerCore) public {
managerCore = _managerCore;
}
/* ============ External Functions ============ */
/**
* ONLY MANAGER: Deletes SetToken/Manager state from extension. Must only be callable by manager!
*/
function removeExtension() external virtual;
/* ============ Internal Functions ============ */
/**
* Invoke call from manager
*
* @param _delegatedManager Manager to interact with
* @param _module Module to interact with
* @param _encoded Encoded byte data
*/
function _invokeManager(IDelegatedManager _delegatedManager, address _module, bytes memory _encoded) internal {
_delegatedManager.interactManager(_module, _encoded);
}
/**
* Internal function to grab manager of passed SetToken from extensions data structure.
*
* @param _setToken SetToken who's manager is needed
*/
function _manager(ISetToken _setToken) internal view returns (IDelegatedManager) {
return setManagers[_setToken];
}
/**
* Internal function to initialize extension to the DelegatedManager.
*
* @param _setToken Instance of the SetToken corresponding to the DelegatedManager
* @param _delegatedManager Instance of the DelegatedManager to initialize
*/
function _initializeExtension(ISetToken _setToken, IDelegatedManager _delegatedManager) internal {
setManagers[_setToken] = _delegatedManager;
_delegatedManager.initializeExtension();
}
/**
* ONLY MANAGER: Internal function to delete SetToken/Manager state from extension
*/
function _removeExtension(ISetToken _setToken, IDelegatedManager _delegatedManager) internal {
require(msg.sender == address(_manager(_setToken)), "Must be Manager");
delete setManagers[_setToken];
emit ExtensionRemoved(address(_setToken), address(_delegatedManager));
}
}
ExplicitERC20.sol 71 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
/**
* @title ExplicitERC20
* @author Set Protocol
*
* Utility functions for ERC20 transfers that require the explicit amount to be transferred.
*/
library ExplicitERC20 {
using SafeMath for uint256;
/**
* When given allowance, transfers a token from the "_from" to the "_to" of quantity "_quantity".
* Ensures that the recipient has received the correct quantity (ie no fees taken on transfer)
*
* @param _token ERC20 token to approve
* @param _from The account to transfer tokens from
* @param _to The account to transfer tokens to
* @param _quantity The quantity to transfer
*/
function transferFrom(
IERC20 _token,
address _from,
address _to,
uint256 _quantity
)
internal
{
// Call specified ERC20 contract to transfer tokens (via proxy).
if (_quantity > 0) {
uint256 existingBalance = _token.balanceOf(_to);
SafeERC20.safeTransferFrom(
_token,
_from,
_to,
_quantity
);
uint256 newBalance = _token.balanceOf(_to);
// Verify transfer quantity is reflected in balance
require(
newBalance == existingBalance.add(_quantity),
"Invalid post transfer balance"
);
}
}
}
Exponential.sol 302 lines
pragma solidity 0.6.10;
/**
* @title Careful Math
* @author Compound
* @notice Derived from OpenZeppelin's SafeMath library
* https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol
*/
contract CarefulMath {
/**
* @dev Possible error codes that we can return
*/
enum MathError {
NO_ERROR,
DIVISION_BY_ZERO,
INTEGER_OVERFLOW,
INTEGER_UNDERFLOW
}
/**
* @dev Multiplies two numbers, returns an error on overflow.
*/
function mulUInt(uint a, uint b) internal pure returns (MathError, uint) {
if (a == 0) {
return (MathError.NO_ERROR, 0);
}
uint c = a * b;
if (c / a != b) {
return (MathError.INTEGER_OVERFLOW, 0);
} else {
return (MathError.NO_ERROR, c);
}
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function divUInt(uint a, uint b) internal pure returns (MathError, uint) {
if (b == 0) {
return (MathError.DIVISION_BY_ZERO, 0);
}
return (MathError.NO_ERROR, a / b);
}
/**
* @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend).
*/
function subUInt(uint a, uint b) internal pure returns (MathError, uint) {
if (b <= a) {
return (MathError.NO_ERROR, a - b);
} else {
return (MathError.INTEGER_UNDERFLOW, 0);
}
}
/**
* @dev Adds two numbers, returns an error on overflow.
*/
function addUInt(uint a, uint b) internal pure returns (MathError, uint) {
uint c = a + b;
if (c >= a) {
return (MathError.NO_ERROR, c);
} else {
return (MathError.INTEGER_OVERFLOW, 0);
}
}
/**
* @dev add a and b and then subtract c
*/
function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) {
(MathError err0, uint sum) = addUInt(a, b);
if (err0 != MathError.NO_ERROR) {
return (err0, 0);
}
return subUInt(sum, c);
}
}
/**
* @title Exponential module for storing fixed-decision decimals
* @author Compound
* @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.
* Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:
* `Exp({mantissa: 5100000000000000000})`.
*/
contract Exponential is CarefulMath {
uint constant expScale = 1e18;
uint constant halfExpScale = expScale/2;
uint constant mantissaOne = expScale;
struct Exp {
uint mantissa;
}
/**
* @dev Creates an exponential from numerator and denominator values.
* Note: Returns an error if (`num` * 10e18) > MAX_INT,
* or if `denom` is zero.
*/
function getExp(uint num, uint denom) pure internal returns (MathError, Exp memory) {
(MathError err0, uint scaledNumerator) = mulUInt(num, expScale);
if (err0 != MathError.NO_ERROR) {
return (err0, Exp({mantissa: 0}));
}
(MathError err1, uint rational) = divUInt(scaledNumerator, denom);
if (err1 != MathError.NO_ERROR) {
return (err1, Exp({mantissa: 0}));
}
return (MathError.NO_ERROR, Exp({mantissa: rational}));
}
/**
* @dev Adds two exponentials, returning a new exponential.
*/
function addExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) {
(MathError error, uint result) = addUInt(a.mantissa, b.mantissa);
return (error, Exp({mantissa: result}));
}
/**
* @dev Subtracts two exponentials, returning a new exponential.
*/
function subExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) {
(MathError error, uint result) = subUInt(a.mantissa, b.mantissa);
return (error, Exp({mantissa: result}));
}
/**
* @dev Multiply an Exp by a scalar, returning a new Exp.
*/
function mulScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) {
(MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar);
if (err0 != MathError.NO_ERROR) {
return (err0, Exp({mantissa: 0}));
}
return (MathError.NO_ERROR, Exp({mantissa: scaledMantissa}));
}
/**
* @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.
*/
function mulScalarTruncate(Exp memory a, uint scalar) pure internal returns (MathError, uint) {
(MathError err, Exp memory product) = mulScalar(a, scalar);
if (err != MathError.NO_ERROR) {
return (err, 0);
}
return (MathError.NO_ERROR, truncate(product));
}
/**
* @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.
*/
function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) pure internal returns (MathError, uint) {
(MathError err, Exp memory product) = mulScalar(a, scalar);
if (err != MathError.NO_ERROR) {
return (err, 0);
}
return addUInt(truncate(product), addend);
}
/**
* @dev Divide an Exp by a scalar, returning a new Exp.
*/
function divScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) {
(MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar);
if (err0 != MathError.NO_ERROR) {
return (err0, Exp({mantissa: 0}));
}
return (MathError.NO_ERROR, Exp({mantissa: descaledMantissa}));
}
/**
* @dev Divide a scalar by an Exp, returning a new Exp.
*/
function divScalarByExp(uint scalar, Exp memory divisor) pure internal returns (MathError, Exp memory) {
/*
We are doing this as:
getExp(mulUInt(expScale, scalar), divisor.mantissa)
How it works:
Exp = a / b;
Scalar = s;
`s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale`
*/
(MathError err0, uint numerator) = mulUInt(expScale, scalar);
if (err0 != MathError.NO_ERROR) {
return (err0, Exp({mantissa: 0}));
}
return getExp(numerator, divisor.mantissa);
}
/**
* @dev Divide a scalar by an Exp, then truncate to return an unsigned integer.
*/
function divScalarByExpTruncate(uint scalar, Exp memory divisor) pure internal returns (MathError, uint) {
(MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor);
if (err != MathError.NO_ERROR) {
return (err, 0);
}
return (MathError.NO_ERROR, truncate(fraction));
}
/**
* @dev Multiplies two exponentials, returning a new exponential.
*/
function mulExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) {
(MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa);
if (err0 != MathError.NO_ERROR) {
return (err0, Exp({mantissa: 0}));
}
// We add half the scale before dividing so that we get rounding instead of truncation.
// See "Listing 6" and text above it at https://accu.org/index.php/journals/1717
// Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18.
(MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct);
if (err1 != MathError.NO_ERROR) {
return (err1, Exp({mantissa: 0}));
}
(MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale);
// The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero.
assert(err2 == MathError.NO_ERROR);
return (MathError.NO_ERROR, Exp({mantissa: product}));
}
/**
* @dev Multiplies two exponentials given their mantissas, returning a new exponential.
*/
function mulExp(uint a, uint b) pure internal returns (MathError, Exp memory) {
return mulExp(Exp({mantissa: a}), Exp({mantissa: b}));
}
/**
* @dev Multiplies three exponentials, returning a new exponential.
*/
function mulExp3(Exp memory a, Exp memory b, Exp memory c) pure internal returns (MathError, Exp memory) {
(MathError err, Exp memory ab) = mulExp(a, b);
if (err != MathError.NO_ERROR) {
return (err, ab);
}
return mulExp(ab, c);
}
/**
* @dev Divides two exponentials, returning a new exponential.
* (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b,
* which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa)
*/
function divExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) {
return getExp(a.mantissa, b.mantissa);
}
/**
* @dev Truncates the given exp to a whole number value.
* For example, truncate(Exp{mantissa: 15 * expScale}) = 15
*/
function truncate(Exp memory exp) pure internal returns (uint) {
// Note: We are not using careful math here as we're performing a division that cannot fail
return exp.mantissa / expScale;
}
/**
* @dev Checks if first Exp is less than second Exp.
*/
function lessThanExp(Exp memory left, Exp memory right) pure internal returns (bool) {
return left.mantissa < right.mantissa; //TODO: Add some simple tests and this in another PR yo.
}
/**
* @dev Checks if left Exp <= right Exp.
*/
function lessThanOrEqualExp(Exp memory left, Exp memory right) pure internal returns (bool) {
return left.mantissa <= right.mantissa;
}
/**
* @dev returns true if Exp is exactly zero
*/
function isZeroExp(Exp memory value) pure internal returns (bool) {
return value.mantissa == 0;
}
}
FlashLoanSimpleReceiverBase.sol 21 lines
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.6.10;
import {IFlashLoanSimpleReceiver} from "../interfaces/IFlashLoanSimpleReceiver.sol";
import {IPoolAddressesProvider} from "../interfaces/IPoolAddressesProvider.sol";
import {IPool} from "../interfaces/IPool.sol";
/**
* @title FlashLoanSimpleReceiverBase
* @author Aave
* @notice Base contract to develop a flashloan-receiver contract.
*/
abstract contract FlashLoanSimpleReceiverBase is IFlashLoanSimpleReceiver {
IPoolAddressesProvider public immutable override ADDRESSES_PROVIDER;
IPool public immutable override POOL;
constructor(IPoolAddressesProvider provider) public {
ADDRESSES_PROVIDER = provider;
POOL = IPool(provider.getPool());
}
}
Invoke.sol 141 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title Invoke
* @author Set Protocol
*
* A collection of common utility functions for interacting with the SetToken's invoke function
*/
library Invoke {
using SafeMath for uint256;
/* ============ Internal ============ */
/**
* Instructs the SetToken to set approvals of the ERC20 token to a spender.
*
* @param _setToken SetToken instance to invoke
* @param _token ERC20 token to approve
* @param _spender The account allowed to spend the SetToken's balance
* @param _quantity The quantity of allowance to allow
*/
function invokeApprove(
ISetToken _setToken,
address _token,
address _spender,
uint256 _quantity
)
internal
{
bytes memory callData = abi.encodeWithSignature("approve(address,uint256)", _spender, _quantity);
_setToken.invoke(_token, 0, callData);
}
/**
* Instructs the SetToken to transfer the ERC20 token to a recipient.
*
* @param _setToken SetToken instance to invoke
* @param _token ERC20 token to transfer
* @param _to The recipient account
* @param _quantity The quantity to transfer
*/
function invokeTransfer(
ISetToken _setToken,
address _token,
address _to,
uint256 _quantity
)
internal
{
if (_quantity > 0) {
bytes memory callData = abi.encodeWithSignature("transfer(address,uint256)", _to, _quantity);
bytes memory returnData = _setToken.invoke(_token, 0, callData);
if (returnData.length > 0) {
require(abi.decode(returnData, (bool)), "ERC20 transfer failed");
}
}
}
/**
* Instructs the SetToken to transfer the ERC20 token to a recipient.
* The new SetToken balance must equal the existing balance less the quantity transferred
*
* @param _setToken SetToken instance to invoke
* @param _token ERC20 token to transfer
* @param _to The recipient account
* @param _quantity The quantity to transfer
*/
function strictInvokeTransfer(
ISetToken _setToken,
address _token,
address _to,
uint256 _quantity
)
internal
{
if (_quantity > 0) {
// Retrieve current balance of token for the SetToken
uint256 existingBalance = IERC20(_token).balanceOf(address(_setToken));
Invoke.invokeTransfer(_setToken, _token, _to, _quantity);
// Get new balance of transferred token for SetToken
uint256 newBalance = IERC20(_token).balanceOf(address(_setToken));
// Verify only the transfer quantity is subtracted
require(
newBalance == existingBalance.sub(_quantity),
"Invalid post transfer balance"
);
}
}
/**
* Instructs the SetToken to unwrap the passed quantity of WETH
*
* @param _setToken SetToken instance to invoke
* @param _weth WETH address
* @param _quantity The quantity to unwrap
*/
function invokeUnwrapWETH(ISetToken _setToken, address _weth, uint256 _quantity) internal {
bytes memory callData = abi.encodeWithSignature("withdraw(uint256)", _quantity);
_setToken.invoke(_weth, 0, callData);
}
/**
* Instructs the SetToken to wrap the passed quantity of ETH
*
* @param _setToken SetToken instance to invoke
* @param _weth WETH address
* @param _quantity The quantity to unwrap
*/
function invokeWrapWETH(ISetToken _setToken, address _weth, uint256 _quantity) internal {
bytes memory callData = abi.encodeWithSignature("deposit()");
_setToken.invoke(_weth, _quantity, callData);
}
}
ModuleBase.sol 237 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { AddressArrayUtils } from "./AddressArrayUtils.sol";
import { ExplicitERC20 } from "./ExplicitERC20.sol";
import { IController } from "../interfaces/IController.sol";
import { IModule } from "../interfaces/IModule.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { Invoke } from "./Invoke.sol";
import { Position } from "./Position.sol";
import { PreciseUnitMath } from "./PreciseUnitMath.sol";
import { ResourceIdentifier } from "./ResourceIdentifier.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
/**
* @title ModuleBase
* @author Set Protocol
*
* Abstract class that houses common Module-related state and functions.
*
* CHANGELOG:
* - 4/21/21: Delegated modifier logic to internal helpers to reduce contract size
*
*/
abstract contract ModuleBase is IModule {
using AddressArrayUtils for address[];
using Invoke for ISetToken;
using Position for ISetToken;
using PreciseUnitMath for uint256;
using ResourceIdentifier for IController;
using SafeCast for int256;
using SafeCast for uint256;
using SafeMath for uint256;
using SignedSafeMath for int256;
/* ============ State Variables ============ */
// Address of the controller
IController public controller;
/* ============ Modifiers ============ */
modifier onlyManagerAndValidSet(ISetToken _setToken) {
_validateOnlyManagerAndValidSet(_setToken);
_;
}
modifier onlySetManager(ISetToken _setToken, address _caller) {
_validateOnlySetManager(_setToken, _caller);
_;
}
modifier onlyValidAndInitializedSet(ISetToken _setToken) {
_validateOnlyValidAndInitializedSet(_setToken);
_;
}
/**
* Throws if the sender is not a SetToken's module or module not enabled
*/
modifier onlyModule(ISetToken _setToken) {
_validateOnlyModule(_setToken);
_;
}
/**
* Utilized during module initializations to check that the module is in pending state
* and that the SetToken is valid
*/
modifier onlyValidAndPendingSet(ISetToken _setToken) {
_validateOnlyValidAndPendingSet(_setToken);
_;
}
/* ============ Constructor ============ */
/**
* Set state variables and map asset pairs to their oracles
*
* @param _controller Address of controller contract
*/
constructor(IController _controller) public {
controller = _controller;
}
/* ============ Internal Functions ============ */
/**
* Transfers tokens from an address (that has set allowance on the module).
*
* @param _token The address of the ERC20 token
* @param _from The address to transfer from
* @param _to The address to transfer to
* @param _quantity The number of tokens to transfer
*/
function transferFrom(IERC20 _token, address _from, address _to, uint256 _quantity) internal {
ExplicitERC20.transferFrom(_token, _from, _to, _quantity);
}
/**
* Gets the integration for the module with the passed in name. Validates that the address is not empty
*/
function getAndValidateAdapter(string memory _integrationName) internal view returns(address) {
bytes32 integrationHash = getNameHash(_integrationName);
return getAndValidateAdapterWithHash(integrationHash);
}
/**
* Gets the integration for the module with the passed in hash. Validates that the address is not empty
*/
function getAndValidateAdapterWithHash(bytes32 _integrationHash) internal view returns(address) {
address adapter = controller.getIntegrationRegistry().getIntegrationAdapterWithHash(
address(this),
_integrationHash
);
require(adapter != address(0), "Must be valid adapter");
return adapter;
}
/**
* Gets the total fee for this module of the passed in index (fee % * quantity)
*/
function getModuleFee(uint256 _feeIndex, uint256 _quantity) internal view returns(uint256) {
uint256 feePercentage = controller.getModuleFee(address(this), _feeIndex);
return _quantity.preciseMul(feePercentage);
}
/**
* Pays the _feeQuantity from the _setToken denominated in _token to the protocol fee recipient
*/
function payProtocolFeeFromSetToken(ISetToken _setToken, address _token, uint256 _feeQuantity) internal {
if (_feeQuantity > 0) {
_setToken.strictInvokeTransfer(_token, controller.feeRecipient(), _feeQuantity);
}
}
/**
* Returns true if the module is in process of initialization on the SetToken
*/
function isSetPendingInitialization(ISetToken _setToken) internal view returns(bool) {
return _setToken.isPendingModule(address(this));
}
/**
* Returns true if the address is the SetToken's manager
*/
function isSetManager(ISetToken _setToken, address _toCheck) internal view returns(bool) {
return _setToken.manager() == _toCheck;
}
/**
* Returns true if SetToken must be enabled on the controller
* and module is registered on the SetToken
*/
function isSetValidAndInitialized(ISetToken _setToken) internal view returns(bool) {
return controller.isSet(address(_setToken)) &&
_setToken.isInitializedModule(address(this));
}
/**
* Hashes the string and returns a bytes32 value
*/
function getNameHash(string memory _name) internal pure returns(bytes32) {
return keccak256(bytes(_name));
}
/* ============== Modifier Helpers ===============
* Internal functions used to reduce bytecode size
*/
/**
* Caller must SetToken manager and SetToken must be valid and initialized
*/
function _validateOnlyManagerAndValidSet(ISetToken _setToken) internal view {
require(isSetManager(_setToken, msg.sender), "Must be the SetToken manager");
require(isSetValidAndInitialized(_setToken), "Must be a valid and initialized SetToken");
}
/**
* Caller must SetToken manager
*/
function _validateOnlySetManager(ISetToken _setToken, address _caller) internal view {
require(isSetManager(_setToken, _caller), "Must be the SetToken manager");
}
/**
* SetToken must be valid and initialized
*/
function _validateOnlyValidAndInitializedSet(ISetToken _setToken) internal view {
require(isSetValidAndInitialized(_setToken), "Must be a valid and initialized SetToken");
}
/**
* Caller must be initialized module and module must be enabled on the controller
*/
function _validateOnlyModule(ISetToken _setToken) internal view {
require(
_setToken.moduleStates(msg.sender) == ISetToken.ModuleState.INITIALIZED,
"Only the module can call"
);
require(
controller.isModule(msg.sender),
"Module must be enabled on controller"
);
}
/**
* SetToken must be in a pending state and module must be in pending state
*/
function _validateOnlyValidAndPendingSet(ISetToken _setToken) internal view {
require(controller.isSet(address(_setToken)), "Must be controller-enabled SetToken");
require(isSetPendingInitialization(_setToken), "Must be pending initialization");
}
}
MutualUpgrade.sol 74 lines
/*
Copyright 2018 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
/**
* @title MutualUpgrade
* @author Set Protocol
*
* The MutualUpgrade contract contains a modifier for handling mutual upgrades between two parties
*/
contract MutualUpgrade {
/* ============ State Variables ============ */
// Mapping of upgradable units and if upgrade has been initialized by other party
mapping(bytes32 => bool) public mutualUpgrades;
/* ============ Events ============ */
event MutualUpgradeRegistered(
bytes32 _upgradeHash
);
/* ============ Modifiers ============ */
modifier mutualUpgrade(address _signerOne, address _signerTwo) {
require(
msg.sender == _signerOne || msg.sender == _signerTwo,
"Must be authorized address"
);
address nonCaller = _getNonCaller(_signerOne, _signerTwo);
// The upgrade hash is defined by the hash of the transaction call data and sender of msg,
// which uniquely identifies the function, arguments, and sender.
bytes32 expectedHash = keccak256(abi.encodePacked(msg.data, nonCaller));
if (!mutualUpgrades[expectedHash]) {
bytes32 newHash = keccak256(abi.encodePacked(msg.data, msg.sender));
mutualUpgrades[newHash] = true;
emit MutualUpgradeRegistered(newHash);
return;
}
delete mutualUpgrades[expectedHash];
// Run the rest of the upgrades
_;
}
/* ============ Internal Functions ============ */
function _getNonCaller(address _signerOne, address _signerTwo) internal view returns(address) {
return msg.sender == _signerOne ? _signerTwo : _signerOne;
}
}
MutualUpgradeV2.sol 82 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
/**
* @title MutualUpgradeV2
* @author Set Protocol
*
* The MutualUpgradeV2 contract contains a modifier for handling mutual upgrades between two parties
*
* CHANGELOG:
* - Update mutualUpgrade to allow single transaction execution if the two signing addresses are the same
*/
contract MutualUpgradeV2 {
/* ============ State Variables ============ */
// Mapping of upgradable units and if upgrade has been initialized by other party
mapping(bytes32 => bool) public mutualUpgrades;
/* ============ Events ============ */
event MutualUpgradeRegistered(
bytes32 _upgradeHash
);
/* ============ Modifiers ============ */
modifier mutualUpgrade(address _signerOne, address _signerTwo) {
require(
msg.sender == _signerOne || msg.sender == _signerTwo,
"Must be authorized address"
);
// If the two signing addresses are the same, skip upgrade hash step
if (_signerOne == _signerTwo) {
_;
}
address nonCaller = _getNonCaller(_signerOne, _signerTwo);
// The upgrade hash is defined by the hash of the transaction call data and sender of msg,
// which uniquely identifies the function, arguments, and sender.
bytes32 expectedHash = keccak256(abi.encodePacked(msg.data, nonCaller));
if (!mutualUpgrades[expectedHash]) {
bytes32 newHash = keccak256(abi.encodePacked(msg.data, msg.sender));
mutualUpgrades[newHash] = true;
emit MutualUpgradeRegistered(newHash);
return;
}
delete mutualUpgrades[expectedHash];
// Run the rest of the upgrades
_;
}
/* ============ Internal Functions ============ */
function _getNonCaller(address _signerOne, address _signerTwo) internal view returns(address) {
return msg.sender == _signerOne ? _signerTwo : _signerOne;
}
}
Position.sol 259 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "./PreciseUnitMath.sol";
/**
* @title Position
* @author Set Protocol
*
* Collection of helper functions for handling and updating SetToken Positions
*
* CHANGELOG:
* - Updated editExternalPosition to work when no external position is associated with module
*/
library Position {
using SafeCast for uint256;
using SafeMath for uint256;
using SafeCast for int256;
using SignedSafeMath for int256;
using PreciseUnitMath for uint256;
/* ============ Helper ============ */
/**
* Returns whether the SetToken has a default position for a given component (if the real unit is > 0)
*/
function hasDefaultPosition(ISetToken _setToken, address _component) internal view returns(bool) {
return _setToken.getDefaultPositionRealUnit(_component) > 0;
}
/**
* Returns whether the SetToken has an external position for a given component (if # of position modules is > 0)
*/
function hasExternalPosition(ISetToken _setToken, address _component) internal view returns(bool) {
return _setToken.getExternalPositionModules(_component).length > 0;
}
/**
* Returns whether the SetToken component default position real unit is greater than or equal to units passed in.
*/
function hasSufficientDefaultUnits(ISetToken _setToken, address _component, uint256 _unit) internal view returns(bool) {
return _setToken.getDefaultPositionRealUnit(_component) >= _unit.toInt256();
}
/**
* Returns whether the SetToken component external position is greater than or equal to the real units passed in.
*/
function hasSufficientExternalUnits(
ISetToken _setToken,
address _component,
address _positionModule,
uint256 _unit
)
internal
view
returns(bool)
{
return _setToken.getExternalPositionRealUnit(_component, _positionModule) >= _unit.toInt256();
}
/**
* If the position does not exist, create a new Position and add to the SetToken. If it already exists,
* then set the position units. If the new units is 0, remove the position. Handles adding/removing of
* components where needed (in light of potential external positions).
*
* @param _setToken Address of SetToken being modified
* @param _component Address of the component
* @param _newUnit Quantity of Position units - must be >= 0
*/
function editDefaultPosition(ISetToken _setToken, address _component, uint256 _newUnit) internal {
bool isPositionFound = hasDefaultPosition(_setToken, _component);
if (!isPositionFound && _newUnit > 0) {
// If there is no Default Position and no External Modules, then component does not exist
if (!hasExternalPosition(_setToken, _component)) {
_setToken.addComponent(_component);
}
} else if (isPositionFound && _newUnit == 0) {
// If there is a Default Position and no external positions, remove the component
if (!hasExternalPosition(_setToken, _component)) {
_setToken.removeComponent(_component);
}
}
_setToken.editDefaultPositionUnit(_component, _newUnit.toInt256());
}
/**
* Update an external position and remove and external positions or components if necessary. The logic flows as follows:
* 1) If component is not already added then add component and external position.
* 2) If component is added but no existing external position using the passed module exists then add the external position.
* 3) If the existing position is being added to then just update the unit and data
* 4) If the position is being closed and no other external positions or default positions are associated with the component
* then untrack the component and remove external position.
* 5) If the position is being closed and other existing positions still exist for the component then just remove the
* external position.
*
* @param _setToken SetToken being updated
* @param _component Component position being updated
* @param _module Module external position is associated with
* @param _newUnit Position units of new external position
* @param _data Arbitrary data associated with the position
*/
function editExternalPosition(
ISetToken _setToken,
address _component,
address _module,
int256 _newUnit,
bytes memory _data
)
internal
{
if (_newUnit != 0) {
if (!_setToken.isComponent(_component)) {
_setToken.addComponent(_component);
_setToken.addExternalPositionModule(_component, _module);
} else if (!_setToken.isExternalPositionModule(_component, _module)) {
_setToken.addExternalPositionModule(_component, _module);
}
_setToken.editExternalPositionUnit(_component, _module, _newUnit);
_setToken.editExternalPositionData(_component, _module, _data);
} else {
require(_data.length == 0, "Passed data must be null");
// If no default or external position remaining then remove component from components array
if (_setToken.getExternalPositionRealUnit(_component, _module) != 0) {
address[] memory positionModules = _setToken.getExternalPositionModules(_component);
if (_setToken.getDefaultPositionRealUnit(_component) == 0 && positionModules.length == 1) {
require(positionModules[0] == _module, "External positions must be 0 to remove component");
_setToken.removeComponent(_component);
}
_setToken.removeExternalPositionModule(_component, _module);
}
}
}
/**
* Get total notional amount of Default position
*
* @param _setTokenSupply Supply of SetToken in precise units (10^18)
* @param _positionUnit Quantity of Position units
*
* @return Total notional amount of units
*/
function getDefaultTotalNotional(uint256 _setTokenSupply, uint256 _positionUnit) internal pure returns (uint256) {
return _setTokenSupply.preciseMul(_positionUnit);
}
/**
* Get position unit from total notional amount
*
* @param _setTokenSupply Supply of SetToken in precise units (10^18)
* @param _totalNotional Total notional amount of component prior to
* @return Default position unit
*/
function getDefaultPositionUnit(uint256 _setTokenSupply, uint256 _totalNotional) internal pure returns (uint256) {
return _totalNotional.preciseDiv(_setTokenSupply);
}
/**
* Get the total tracked balance - total supply * position unit
*
* @param _setToken Address of the SetToken
* @param _component Address of the component
* @return Notional tracked balance
*/
function getDefaultTrackedBalance(ISetToken _setToken, address _component) internal view returns(uint256) {
int256 positionUnit = _setToken.getDefaultPositionRealUnit(_component);
return _setToken.totalSupply().preciseMul(positionUnit.toUint256());
}
/**
* Calculates the new default position unit and performs the edit with the new unit
*
* @param _setToken Address of the SetToken
* @param _component Address of the component
* @param _setTotalSupply Current SetToken supply
* @param _componentPreviousBalance Pre-action component balance
* @return Current component balance
* @return Previous position unit
* @return New position unit
*/
function calculateAndEditDefaultPosition(
ISetToken _setToken,
address _component,
uint256 _setTotalSupply,
uint256 _componentPreviousBalance
)
internal
returns(uint256, uint256, uint256)
{
uint256 currentBalance = IERC20(_component).balanceOf(address(_setToken));
uint256 positionUnit = _setToken.getDefaultPositionRealUnit(_component).toUint256();
uint256 newTokenUnit;
if (currentBalance > 0) {
newTokenUnit = calculateDefaultEditPositionUnit(
_setTotalSupply,
_componentPreviousBalance,
currentBalance,
positionUnit
);
} else {
newTokenUnit = 0;
}
editDefaultPosition(_setToken, _component, newTokenUnit);
return (currentBalance, positionUnit, newTokenUnit);
}
/**
* Calculate the new position unit given total notional values pre and post executing an action that changes SetToken state
* The intention is to make updates to the units without accidentally picking up airdropped assets as well.
*
* @param _setTokenSupply Supply of SetToken in precise units (10^18)
* @param _preTotalNotional Total notional amount of component prior to executing action
* @param _postTotalNotional Total notional amount of component after the executing action
* @param _prePositionUnit Position unit of SetToken prior to executing action
* @return New position unit
*/
function calculateDefaultEditPositionUnit(
uint256 _setTokenSupply,
uint256 _preTotalNotional,
uint256 _postTotalNotional,
uint256 _prePositionUnit
)
internal
pure
returns (uint256)
{
// If pre action total notional amount is greater then subtract post action total notional and calculate new position units
uint256 airdroppedAmount = _preTotalNotional.sub(_prePositionUnit.preciseMul(_setTokenSupply));
return _postTotalNotional.sub(airdroppedAmount).preciseDiv(_setTokenSupply);
}
}
PreciseUnitMath.sol 190 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
/**
* @title PreciseUnitMath
* @author Set Protocol
*
* Arithmetic for fixed-point numbers with 18 decimals of precision. Some functions taken from
* dYdX's BaseMath library.
*
* CHANGELOG:
* - 9/21/20: Added safePower function
*/
library PreciseUnitMath {
using SafeMath for uint256;
using SignedSafeMath for int256;
// The number One in precise units.
uint256 constant internal PRECISE_UNIT = 10 ** 18;
int256 constant internal PRECISE_UNIT_INT = 10 ** 18;
// Max unsigned integer value
uint256 constant internal MAX_UINT_256 = type(uint256).max;
// Max and min signed integer value
int256 constant internal MAX_INT_256 = type(int256).max;
int256 constant internal MIN_INT_256 = type(int256).min;
/**
* @dev Getter function since constants can't be read directly from libraries.
*/
function preciseUnit() internal pure returns (uint256) {
return PRECISE_UNIT;
}
/**
* @dev Getter function since constants can't be read directly from libraries.
*/
function preciseUnitInt() internal pure returns (int256) {
return PRECISE_UNIT_INT;
}
/**
* @dev Getter function since constants can't be read directly from libraries.
*/
function maxUint256() internal pure returns (uint256) {
return MAX_UINT_256;
}
/**
* @dev Getter function since constants can't be read directly from libraries.
*/
function maxInt256() internal pure returns (int256) {
return MAX_INT_256;
}
/**
* @dev Getter function since constants can't be read directly from libraries.
*/
function minInt256() internal pure returns (int256) {
return MIN_INT_256;
}
/**
* @dev Multiplies value a by value b (result is rounded down). It's assumed that the value b is the significand
* of a number with 18 decimals precision.
*/
function preciseMul(uint256 a, uint256 b) internal pure returns (uint256) {
return a.mul(b).div(PRECISE_UNIT);
}
/**
* @dev Multiplies value a by value b (result is rounded towards zero). It's assumed that the value b is the
* significand of a number with 18 decimals precision.
*/
function preciseMul(int256 a, int256 b) internal pure returns (int256) {
return a.mul(b).div(PRECISE_UNIT_INT);
}
/**
* @dev Multiplies value a by value b (result is rounded up). It's assumed that the value b is the significand
* of a number with 18 decimals precision.
*/
function preciseMulCeil(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0 || b == 0) {
return 0;
}
return a.mul(b).sub(1).div(PRECISE_UNIT).add(1);
}
/**
* @dev Divides value a by value b (result is rounded down).
*/
function preciseDiv(uint256 a, uint256 b) internal pure returns (uint256) {
return a.mul(PRECISE_UNIT).div(b);
}
/**
* @dev Divides value a by value b (result is rounded towards 0).
*/
function preciseDiv(int256 a, int256 b) internal pure returns (int256) {
return a.mul(PRECISE_UNIT_INT).div(b);
}
/**
* @dev Divides value a by value b (result is rounded up or away from 0).
*/
function preciseDivCeil(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "Cant divide by 0");
return a > 0 ? a.mul(PRECISE_UNIT).sub(1).div(b).add(1) : 0;
}
/**
* @dev Divides value a by value b (result is rounded down - positive numbers toward 0 and negative away from 0).
*/
function divDown(int256 a, int256 b) internal pure returns (int256) {
require(b != 0, "Cant divide by 0");
require(a != MIN_INT_256 || b != -1, "Invalid input");
int256 result = a.div(b);
if (a ^ b < 0 && a % b != 0) {
result -= 1;
}
return result;
}
/**
* @dev Multiplies value a by value b where rounding is towards the lesser number.
* (positive values are rounded towards zero and negative values are rounded away from 0).
*/
function conservativePreciseMul(int256 a, int256 b) internal pure returns (int256) {
return divDown(a.mul(b), PRECISE_UNIT_INT);
}
/**
* @dev Divides value a by value b where rounding is towards the lesser number.
* (positive values are rounded towards zero and negative values are rounded away from 0).
*/
function conservativePreciseDiv(int256 a, int256 b) internal pure returns (int256) {
return divDown(a.mul(PRECISE_UNIT_INT), b);
}
/**
* @dev Performs the power on a specified value, reverts on overflow.
*/
function safePower(
uint256 a,
uint256 pow
)
internal
pure
returns (uint256)
{
require(a > 0, "Value must be positive");
uint256 result = 1;
for (uint256 i = 0; i < pow; i++){
uint256 previousResult = result;
// Using safemath multiplication prevents overflows
result = previousResult.mul(a);
}
return result;
}
}
ResourceIdentifier.sol 64 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { IController } from "../interfaces/IController.sol";
import { IIntegrationRegistry } from "../interfaces/IIntegrationRegistry.sol";
import { IPriceOracle } from "../interfaces/IPriceOracle.sol";
import { ISetValuer } from "../interfaces/ISetValuer.sol";
/**
* @title ResourceIdentifier
* @author Set Protocol
*
* A collection of utility functions to fetch information related to Resource contracts in the system
*/
library ResourceIdentifier {
// IntegrationRegistry will always be resource ID 0 in the system
uint256 constant internal INTEGRATION_REGISTRY_RESOURCE_ID = 0;
// PriceOracle will always be resource ID 1 in the system
uint256 constant internal PRICE_ORACLE_RESOURCE_ID = 1;
// SetValuer resource will always be resource ID 2 in the system
uint256 constant internal SET_VALUER_RESOURCE_ID = 2;
/* ============ Internal ============ */
/**
* Gets the instance of integration registry stored on Controller. Note: IntegrationRegistry is stored as index 0 on
* the Controller
*/
function getIntegrationRegistry(IController _controller) internal view returns (IIntegrationRegistry) {
return IIntegrationRegistry(_controller.resourceId(INTEGRATION_REGISTRY_RESOURCE_ID));
}
/**
* Gets instance of price oracle on Controller. Note: PriceOracle is stored as index 1 on the Controller
*/
function getPriceOracle(IController _controller) internal view returns (IPriceOracle) {
return IPriceOracle(_controller.resourceId(PRICE_ORACLE_RESOURCE_ID));
}
/**
* Gets the instance of Set valuer on Controller. Note: SetValuer is stored as index 2 on the Controller
*/
function getSetValuer(IController _controller) internal view returns (ISetValuer) {
return ISetValuer(_controller.resourceId(SET_VALUER_RESOURCE_ID));
}
}
StringArrayUtils.sol 61 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
/**
* @title StringArrayUtils
* @author Set Protocol
*
* Utility functions to handle String Arrays
*/
library StringArrayUtils {
/**
* Finds the index of the first occurrence of the given element.
* @param A The input string to search
* @param a The value to find
* @return Returns (index and isIn) for the first occurrence starting from index 0
*/
function indexOf(string[] memory A, string memory a) internal pure returns (uint256, bool) {
uint256 length = A.length;
for (uint256 i = 0; i < length; i++) {
if (keccak256(bytes(A[i])) == keccak256(bytes(a))) {
return (i, true);
}
}
return (uint256(-1), false);
}
/**
* @param A The input array to search
* @param a The string to remove
*/
function removeStorage(string[] storage A, string memory a)
internal
{
(uint256 index, bool isIn) = indexOf(A, a);
if (!isIn) {
revert("String not in array.");
} else {
uint256 lastIndex = A.length - 1; // If the array would be empty, the previous line would throw, so no underflow here
if (index != lastIndex) { A[index] = A[lastIndex]; }
A.pop();
}
}
}
TimeLockUpgrade.sol 117 lines
/*
Copyright 2018 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
/**
* @title TimeLockUpgrade
* @author Set Protocol
*
* The TimeLockUpgrade contract contains a modifier for handling minimum time period updates
*/
contract TimeLockUpgrade is
Ownable
{
using SafeMath for uint256;
/* ============ State Variables ============ */
// Timelock Upgrade Period in seconds
uint256 public timeLockPeriod;
// Mapping of upgradable units and initialized timelock
mapping(bytes32 => uint256) public timeLockedUpgrades;
/* ============ Events ============ */
event UpgradeRegistered(
bytes32 _upgradeHash,
uint256 _timestamp
);
/* ============ Modifiers ============ */
modifier timeLockUpgrade() {
// If the time lock period is 0, then allow non-timebound upgrades.
// This is useful for initialization of the protocol and for testing.
if (timeLockPeriod == 0) {
_;
return;
}
// The upgrade hash is defined by the hash of the transaction call data,
// which uniquely identifies the function as well as the passed in arguments.
bytes32 upgradeHash = keccak256(
abi.encodePacked(
msg.data
)
);
uint256 registrationTime = timeLockedUpgrades[upgradeHash];
// If the upgrade hasn't been registered, register with the current time.
if (registrationTime == 0) {
timeLockedUpgrades[upgradeHash] = block.timestamp;
emit UpgradeRegistered(
upgradeHash,
block.timestamp
);
return;
}
require(
block.timestamp >= registrationTime.add(timeLockPeriod),
"TimeLockUpgrade: Time lock period must have elapsed."
);
// Reset the timestamp to 0
timeLockedUpgrades[upgradeHash] = 0;
// Run the rest of the upgrades
_;
}
/* ============ Function ============ */
/**
* Change timeLockPeriod period. Generally called after initially settings have been set up.
*
* @param _timeLockPeriod Time in seconds that upgrades need to be evaluated before execution
*/
function setTimeLockPeriod(
uint256 _timeLockPeriod
)
virtual
external
onlyOwner
{
// Only allow setting of the timeLockPeriod if the period is greater than the existing
require(
_timeLockPeriod > timeLockPeriod,
"TimeLockUpgrade: New period must be greater than existing"
);
timeLockPeriod = _timeLockPeriod;
}
}
TransferHelper.sol 76 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
library TransferHelper {
/// @notice Transfers tokens from the targeted address to the given destination
/// @notice Errors with 'STF' if transfer fails
/// @param token The contract address of the token to be transferred
/// @param from The originating address from which the tokens will be transferred
/// @param to The destination address of the transfer
/// @param value The amount to be transferred
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "STF");
}
/// @notice Transfers tokens from msg.sender to a recipient
/// @dev Errors with ST if transfer fails
/// @param token The contract address of the token which will be transferred
/// @param to The recipient of the transfer
/// @param value The value of the transfer
function safeTransfer(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "ST");
}
/// @notice Approves the stipulated contract to spend the given allowance in the given token
/// @dev Errors with 'SA' if transfer fails
/// @param token The contract address of the token to be approved
/// @param to The target of the approval
/// @param value The amount of the given token the target will be allowed to spend
function safeApprove(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "SA");
}
/// @notice Transfers ETH to the recipient address
/// @dev Fails with `STE`
/// @param to The destination of the transfer
/// @param value The value to be transferred
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, "STE");
}
}
BaseManager.sol 215 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { IAdapter } from "../interfaces/IAdapter.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title BaseManager
* @author Set Protocol
*
* Smart contract manager that contains permissions and admin functionality
*/
contract BaseManager {
using Address for address;
using AddressArrayUtils for address[];
/* ============ Events ============ */
event AdapterAdded(
address _adapter
);
event AdapterRemoved(
address _adapter
);
event MethodologistChanged(
address _oldMethodologist,
address _newMethodologist
);
event OperatorChanged(
address _oldOperator,
address _newOperator
);
/* ============ Modifiers ============ */
/**
* Throws if the sender is not the SetToken operator
*/
modifier onlyOperator() {
require(msg.sender == operator, "Must be operator");
_;
}
/**
* Throws if the sender is not the SetToken methodologist
*/
modifier onlyMethodologist() {
require(msg.sender == methodologist, "Must be methodologist");
_;
}
/**
* Throws if the sender is not a listed adapter
*/
modifier onlyAdapter() {
require(isAdapter[msg.sender], "Must be adapter");
_;
}
/* ============ State Variables ============ */
// Instance of SetToken
ISetToken public setToken;
// Array of listed adapters
address[] internal adapters;
// Mapping to check if adapter is added
mapping(address => bool) public isAdapter;
// Address of operator which typically executes manager only functions on Set Protocol modules
address public operator;
// Address of methodologist which serves as providing methodology for the index
address public methodologist;
/* ============ Constructor ============ */
constructor(
ISetToken _setToken,
address _operator,
address _methodologist
)
public
{
setToken = _setToken;
operator = _operator;
methodologist = _methodologist;
}
/* ============ External Functions ============ */
/**
* MUTUAL UPGRADE: Update the SetToken manager address. Operator and Methodologist must each call
* this function to execute the update.
*
* @param _newManager New manager address
*/
function setManager(address _newManager) external onlyOperator {
require(_newManager != address(0), "Zero address not valid");
setToken.setManager(_newManager);
}
/**
* MUTUAL UPGRADE: Add a new adapter that the BaseManager can call.
*
* @param _adapter New adapter to add
*/
function addAdapter(address _adapter) external onlyOperator {
require(!isAdapter[_adapter], "Adapter already exists");
require(address(IAdapter(_adapter).manager()) == address(this), "Adapter manager invalid");
adapters.push(_adapter);
isAdapter[_adapter] = true;
emit AdapterAdded(_adapter);
}
/**
* MUTUAL UPGRADE: Remove an existing adapter tracked by the BaseManager.
*
* @param _adapter Old adapter to remove
*/
function removeAdapter(address _adapter) external onlyOperator {
require(isAdapter[_adapter], "Adapter does not exist");
adapters.removeStorage(_adapter);
isAdapter[_adapter] = false;
emit AdapterRemoved(_adapter);
}
/**
* ADAPTER ONLY: Interact with a module registered on the SetToken.
*
* @param _module Module to interact with
* @param _data Byte data of function to call in module
*/
function interactManager(address _module, bytes calldata _data) external onlyAdapter {
// Invoke call to module, assume value will always be 0
_module.functionCallWithValue(_data, 0);
}
/**
* OPERATOR ONLY: Add a new module to the SetToken.
*
* @param _module New module to add
*/
function addModule(address _module) external onlyOperator {
setToken.addModule(_module);
}
/**
* OPERATOR ONLY: Remove a new module from the SetToken.
*
* @param _module Module to remove
*/
function removeModule(address _module) external onlyOperator {
setToken.removeModule(_module);
}
/**
* METHODOLOGIST ONLY: Update the methodologist address
*
* @param _newMethodologist New methodologist address
*/
function setMethodologist(address _newMethodologist) external onlyMethodologist {
emit MethodologistChanged(methodologist, _newMethodologist);
methodologist = _newMethodologist;
}
/**
* OPERATOR ONLY: Update the operator address
*
* @param _newOperator New operator address
*/
function setOperator(address _newOperator) external onlyOperator {
emit OperatorChanged(operator, _newOperator);
operator = _newOperator;
}
/* ============ External Getters ============ */
function getAdapters() external view returns(address[] memory) {
return adapters;
}
}
BaseManagerV2.sol 613 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { IExtension } from "../interfaces/IExtension.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { MutualUpgrade } from "../lib/MutualUpgrade.sol";
/**
* @title BaseManagerV2
* @author Set Protocol
*
* Smart contract manager that contains permissions and admin functionality. Implements IIP-64, supporting
* a registry of protected modules that can only be upgraded with methodologist consent.
*/
contract BaseManagerV2 is MutualUpgrade {
using Address for address;
using AddressArrayUtils for address[];
using SafeERC20 for IERC20;
/* ============ Struct ========== */
struct ProtectedModule {
bool isProtected; // Flag set to true if module is protected
address[] authorizedExtensionsList; // List of Extensions authorized to call module
mapping(address => bool) authorizedExtensions; // Map of extensions authorized to call module
}
/* ============ Events ============ */
event ExtensionAdded(
address _extension
);
event ExtensionRemoved(
address _extension
);
event MethodologistChanged(
address _oldMethodologist,
address _newMethodologist
);
event OperatorChanged(
address _oldOperator,
address _newOperator
);
event ExtensionAuthorized(
address _module,
address _extension
);
event ExtensionAuthorizationRevoked(
address _module,
address _extension
);
event ModuleProtected(
address _module,
address[] _extensions
);
event ModuleUnprotected(
address _module
);
event ReplacedProtectedModule(
address _oldModule,
address _newModule,
address[] _newExtensions
);
event EmergencyReplacedProtectedModule(
address _module,
address[] _extensions
);
event EmergencyRemovedProtectedModule(
address _module
);
event EmergencyResolved();
/* ============ Modifiers ============ */
/**
* Throws if the sender is not the SetToken operator
*/
modifier onlyOperator() {
require(msg.sender == operator, "Must be operator");
_;
}
/**
* Throws if the sender is not the SetToken methodologist
*/
modifier onlyMethodologist() {
require(msg.sender == methodologist, "Must be methodologist");
_;
}
/**
* Throws if the sender is not a listed extension
*/
modifier onlyExtension() {
require(isExtension[msg.sender], "Must be extension");
_;
}
/**
* Throws if contract is in an emergency state following a unilateral operator removal of a
* protected module.
*/
modifier upgradesPermitted() {
require(emergencies == 0, "Upgrades paused by emergency");
_;
}
/**
* Throws if contract is *not* in an emergency state. Emergency replacement and resolution
* can only happen in an emergency
*/
modifier onlyEmergency() {
require(emergencies > 0, "Not in emergency");
_;
}
/* ============ State Variables ============ */
// Instance of SetToken
ISetToken public setToken;
// Array of listed extensions
address[] internal extensions;
// Mapping to check if extension is added
mapping(address => bool) public isExtension;
// Address of operator which typically executes manager only functions on Set Protocol modules
address public operator;
// Address of methodologist which serves as providing methodology for the index
address public methodologist;
// Counter incremented when the operator "emergency removes" a protected module. Decremented
// when methodologist executes an "emergency replacement". Operator can only add modules and
// extensions when `emergencies` is zero. Emergencies can only be declared "over" by mutual agreement
// between operator and methodologist or by the methodologist alone via `resolveEmergency`
uint256 public emergencies;
// Mapping of protected modules. These cannot be called or removed except by mutual upgrade.
mapping(address => ProtectedModule) public protectedModules;
// List of protected modules, for iteration. Used when checking that an extension removal
// can happen without methodologist approval
address[] public protectedModulesList;
// Boolean set when methodologist authorizes initialization after contract deployment.
// Must be true to call via `interactManager`.
bool public initialized;
/* ============ Constructor ============ */
constructor(
ISetToken _setToken,
address _operator,
address _methodologist
)
public
{
setToken = _setToken;
operator = _operator;
methodologist = _methodologist;
}
/* ============ External Functions ============ */
/**
* ONLY METHODOLOGIST : Called by the methodologist to enable contract. All `interactManager`
* calls revert until this is invoked. Lets methodologist review and authorize initial protected
* module settings.
*/
function authorizeInitialization() external onlyMethodologist {
require(!initialized, "Initialization authorized");
initialized = true;
}
/**
* MUTUAL UPGRADE: Update the SetToken manager address. Operator and Methodologist must each call
* this function to execute the update.
*
* @param _newManager New manager address
*/
function setManager(address _newManager) external mutualUpgrade(operator, methodologist) {
require(_newManager != address(0), "Zero address not valid");
setToken.setManager(_newManager);
}
/**
* OPERATOR ONLY: Add a new extension that the BaseManager can call.
*
* @param _extension New extension to add
*/
function addExtension(address _extension) external upgradesPermitted onlyOperator {
require(!isExtension[_extension], "Extension already exists");
require(address(IExtension(_extension).manager()) == address(this), "Extension manager invalid");
_addExtension(_extension);
}
/**
* OPERATOR ONLY: Remove an existing extension tracked by the BaseManager.
*
* @param _extension Old extension to remove
*/
function removeExtension(address _extension) external onlyOperator {
require(isExtension[_extension], "Extension does not exist");
require(!_isAuthorizedExtension(_extension), "Extension used by protected module");
extensions.removeStorage(_extension);
isExtension[_extension] = false;
emit ExtensionRemoved(_extension);
}
/**
* MUTUAL UPGRADE: Authorizes an extension for a protected module. Operator and Methodologist must
* each call this function to execute the update. Adds extension to manager if not already present.
*
* @param _module Module to authorize extension for
* @param _extension Extension to authorize for module
*/
function authorizeExtension(address _module, address _extension)
external
mutualUpgrade(operator, methodologist)
{
require(protectedModules[_module].isProtected, "Module not protected");
require(!protectedModules[_module].authorizedExtensions[_extension], "Extension already authorized");
_authorizeExtension(_module, _extension);
emit ExtensionAuthorized(_module, _extension);
}
/**
* MUTUAL UPGRADE: Revokes extension authorization for a protected module. Operator and Methodologist
* must each call this function to execute the update. In order to remove the extension completely
* from the contract removeExtension must be called by the operator.
*
* @param _module Module to revoke extension authorization for
* @param _extension Extension to revoke authorization of
*/
function revokeExtensionAuthorization(address _module, address _extension)
external
mutualUpgrade(operator, methodologist)
{
require(protectedModules[_module].isProtected, "Module not protected");
require(isExtension[_extension], "Extension does not exist");
require(protectedModules[_module].authorizedExtensions[_extension], "Extension not authorized");
protectedModules[_module].authorizedExtensions[_extension] = false;
protectedModules[_module].authorizedExtensionsList.removeStorage(_extension);
emit ExtensionAuthorizationRevoked(_module, _extension);
}
/**
* ADAPTER ONLY: Interact with a module registered on the SetToken. Manager initialization must
* have been authorized by methodologist. Extension making this call must be authorized
* to call module if module is protected.
*
* @param _module Module to interact with
* @param _data Byte data of function to call in module
*/
function interactManager(address _module, bytes memory _data) external onlyExtension {
require(initialized, "Manager not initialized");
require(_module != address(setToken), "Extensions cannot call SetToken");
require(_senderAuthorizedForModule(_module, msg.sender), "Extension not authorized for module");
// Invoke call to module, assume value will always be 0
_module.functionCallWithValue(_data, 0);
}
/**
* OPERATOR ONLY: Transfers _tokens held by the manager to _destination. Can be used to
* recover anything sent here accidentally. In BaseManagerV2, extensions should
* be the only contracts designated as `feeRecipient` in fee modules.
*
* @param _token ERC20 token to send
* @param _destination Address receiving the tokens
* @param _amount Quantity of tokens to send
*/
function transferTokens(address _token, address _destination, uint256 _amount) external onlyExtension {
IERC20(_token).safeTransfer(_destination, _amount);
}
/**
* OPERATOR ONLY: Add a new module to the SetToken.
*
* @param _module New module to add
*/
function addModule(address _module) external upgradesPermitted onlyOperator {
setToken.addModule(_module);
}
/**
* OPERATOR ONLY: Remove a new module from the SetToken. Any extensions associated with this
* module need to be removed in separate transactions via removeExtension.
*
* @param _module Module to remove
*/
function removeModule(address _module) external onlyOperator {
require(!protectedModules[_module].isProtected, "Module protected");
setToken.removeModule(_module);
}
/**
* OPERATOR ONLY: Marks a currently protected module as unprotected and deletes its authorized
* extension registries. Removes module from the SetToken. Increments the `emergencies` counter,
* prohibiting any operator-only module or extension additions until `emergencyReplaceProtectedModule`
* is executed or `resolveEmergency` is called by the methodologist.
*
* Called by operator when a module must be removed immediately for security reasons and it's unsafe
* to wait for a `mutualUpgrade` process to play out.
*
* NOTE: If removing a fee module, you can ensure all fees are distributed by calling distribute
* on the module's de-authorized fee extension after this call.
*
* @param _module Module to remove
*/
function emergencyRemoveProtectedModule(address _module) external onlyOperator {
_unProtectModule(_module);
setToken.removeModule(_module);
emergencies += 1;
emit EmergencyRemovedProtectedModule(_module);
}
/**
* OPERATOR ONLY: Marks an existing module as protected and authorizes extensions for
* it, adding them if necessary. Adds module to the protected modules list
*
* The operator uses this when they're adding new features and want to assure the methodologist
* they won't be unilaterally changed in the future. Cannot be called during an emergency because
* methodologist needs to explicitly approve protection arrangements under those conditions.
*
* NOTE: If adding a fee extension while protecting a fee module, it's important to set the
* module `feeRecipient` to the new extension's address (ideally before this call).
*
* @param _module Module to protect
* @param _extensions Array of extensions to authorize for protected module
*/
function protectModule(address _module, address[] memory _extensions)
external
upgradesPermitted
onlyOperator
{
require(setToken.getModules().contains(_module), "Module not added yet");
_protectModule(_module, _extensions);
emit ModuleProtected(_module, _extensions);
}
/**
* METHODOLOGIST ONLY: Marks a currently protected module as unprotected and deletes its authorized
* extension registries. Removes old module from the protected modules list.
*
* Called by the methodologist when they want to cede control over a protected module without triggering
* an emergency (for example, to remove it because its dead).
*
* @param _module Module to revoke protections for
*/
function unProtectModule(address _module) external onlyMethodologist {
_unProtectModule(_module);
emit ModuleUnprotected(_module);
}
/**
* MUTUAL UPGRADE: Replaces a protected module. Operator and Methodologist must each call this
* function to execute the update.
*
* > Marks a currently protected module as unprotected
* > Deletes its authorized extension registries.
* > Removes old module from SetToken.
* > Adds new module to SetToken.
* > Marks `_newModule` as protected and authorizes new extensions for it.
*
* Used when methodologists wants to guarantee that an existing protection arrangement is replaced
* with a suitable substitute (ex: upgrading a StreamingFeeSplitExtension).
*
* NOTE: If replacing a fee module, it's necessary to set the module `feeRecipient` to be
* the new fee extension address after this call. Any fees remaining in the old module's
* de-authorized extensions can be distributed by calling `distribute()` on the old extension.
*
* @param _oldModule Module to remove
* @param _newModule Module to add in place of removed module
* @param _newExtensions Extensions to authorize for new module
*/
function replaceProtectedModule(address _oldModule, address _newModule, address[] memory _newExtensions)
external
mutualUpgrade(operator, methodologist)
{
_unProtectModule(_oldModule);
setToken.removeModule(_oldModule);
setToken.addModule(_newModule);
_protectModule(_newModule, _newExtensions);
emit ReplacedProtectedModule(_oldModule, _newModule, _newExtensions);
}
/**
* MUTUAL UPGRADE & EMERGENCY ONLY: Replaces a module the operator has removed with
* `emergencyRemoveProtectedModule`. Operator and Methodologist must each call this function to
* execute the update.
*
* > Adds new module to SetToken.
* > Marks `_newModule` as protected and authorizes new extensions for it.
* > Adds `_newModule` to protectedModules list.
* > Decrements the emergencies counter,
*
* Used when methodologist wants to guarantee that a protection arrangement which was
* removed in an emergency is replaced with a suitable substitute. Operator's ability to add modules
* or extensions is restored after invoking this method (if this is the only emergency.)
*
* NOTE: If replacing a fee module, it's necessary to set the module `feeRecipient` to be
* the new fee extension address after this call. Any fees remaining in the old module's
* de-authorized extensions can be distributed by calling `accrueFeesAndDistribute` on the old extension.
*
* @param _module Module to add in place of removed module
* @param _extensions Array of extensions to authorize for replacement module
*/
function emergencyReplaceProtectedModule(
address _module,
address[] memory _extensions
)
external
mutualUpgrade(operator, methodologist)
onlyEmergency
{
setToken.addModule(_module);
_protectModule(_module, _extensions);
emergencies -= 1;
emit EmergencyReplacedProtectedModule(_module, _extensions);
}
/**
* METHODOLOGIST ONLY & EMERGENCY ONLY: Decrements the emergencies counter.
*
* Allows a methodologist to exit a state of emergency without replacing a protected module that
* was removed. This could happen if the module has no viable substitute or operator and methodologist
* agree that restoring normal operations is the best way forward.
*/
function resolveEmergency() external onlyMethodologist onlyEmergency {
emergencies -= 1;
emit EmergencyResolved();
}
/**
* METHODOLOGIST ONLY: Update the methodologist address
*
* @param _newMethodologist New methodologist address
*/
function setMethodologist(address _newMethodologist) external onlyMethodologist {
emit MethodologistChanged(methodologist, _newMethodologist);
methodologist = _newMethodologist;
}
/**
* OPERATOR ONLY: Update the operator address
*
* @param _newOperator New operator address
*/
function setOperator(address _newOperator) external onlyOperator {
emit OperatorChanged(operator, _newOperator);
operator = _newOperator;
}
/* ============ External Getters ============ */
function getExtensions() external view returns(address[] memory) {
return extensions;
}
function getAuthorizedExtensions(address _module) external view returns (address[] memory) {
return protectedModules[_module].authorizedExtensionsList;
}
function isAuthorizedExtension(address _module, address _extension) external view returns (bool) {
return protectedModules[_module].authorizedExtensions[_extension];
}
function getProtectedModules() external view returns (address[] memory) {
return protectedModulesList;
}
/* ============ Internal ============ */
/**
* Add a new extension that the BaseManager can call.
*/
function _addExtension(address _extension) internal {
extensions.push(_extension);
isExtension[_extension] = true;
emit ExtensionAdded(_extension);
}
/**
* Marks a currently protected module as unprotected and deletes it from authorized extension
* registries. Removes module from the SetToken.
*/
function _unProtectModule(address _module) internal {
require(protectedModules[_module].isProtected, "Module not protected");
// Clear mapping and array entries in struct before deleting mapping entry
for (uint256 i = 0; i < protectedModules[_module].authorizedExtensionsList.length; i++) {
address extension = protectedModules[_module].authorizedExtensionsList[i];
protectedModules[_module].authorizedExtensions[extension] = false;
}
delete protectedModules[_module];
protectedModulesList.removeStorage(_module);
}
/**
* Adds new module to SetToken. Marks `_newModule` as protected and authorizes
* new extensions for it. Adds `_newModule` module to protectedModules list.
*/
function _protectModule(address _module, address[] memory _extensions) internal {
require(!protectedModules[_module].isProtected, "Module already protected");
protectedModules[_module].isProtected = true;
protectedModulesList.push(_module);
for (uint i = 0; i < _extensions.length; i++) {
_authorizeExtension(_module, _extensions[i]);
}
}
/**
* Adds extension if not already added and marks extension as authorized for module
*/
function _authorizeExtension(address _module, address _extension) internal {
if (!isExtension[_extension]) {
_addExtension(_extension);
}
protectedModules[_module].authorizedExtensions[_extension] = true;
protectedModules[_module].authorizedExtensionsList.push(_extension);
}
/**
* Searches the extension mappings of each protected modules to determine if an extension
* is authorized by any of them. Authorized extensions cannot be unilaterally removed by
* the operator.
*/
function _isAuthorizedExtension(address _extension) internal view returns (bool) {
for (uint256 i = 0; i < protectedModulesList.length; i++) {
if (protectedModules[protectedModulesList[i]].authorizedExtensions[_extension]) {
return true;
}
}
return false;
}
/**
* Checks if `_sender` (an extension) is allowed to call a module (which may be protected)
*/
function _senderAuthorizedForModule(address _module, address _sender) internal view returns (bool) {
if (protectedModules[_module].isProtected) {
return protectedModules[_module].authorizedExtensions[_sender];
}
return true;
}
}
DelegatedManager.sol 478 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { AddressArrayUtils } from "../lib/AddressArrayUtils.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { IGlobalExtension } from "../interfaces/IGlobalExtension.sol";
import { MutualUpgradeV2 } from "../lib/MutualUpgradeV2.sol";
/**
* @title DelegatedManager
* @author Set Protocol
*
* Smart contract manager that maintains permissions and SetToken admin functionality via owner role. Owner
* works alongside methodologist to ensure business agreements are kept. Owner is able to delegate maintenance
* operations to operator(s). There can be more than one operator, however they have a global role so once
* delegated to they can perform any operator delegated roles. The owner is able to set restrictions on what
* operators can do in the form of asset whitelists. Operators cannot trade/wrap/claim/etc. an asset that is not
* a part of the asset whitelist, hence they are a semi-trusted party. It is recommended that the owner address
* be managed by a multi-sig or some form of permissioning system.
*/
contract DelegatedManager is Ownable, MutualUpgradeV2 {
using Address for address;
using AddressArrayUtils for address[];
using SafeERC20 for IERC20;
/* ============ Enums ============ */
enum ExtensionState {
NONE,
PENDING,
INITIALIZED
}
/* ============ Events ============ */
event MethodologistChanged(
address indexed _newMethodologist
);
event ExtensionAdded(
address indexed _extension
);
event ExtensionRemoved(
address indexed _extension
);
event ExtensionInitialized(
address indexed _extension
);
event OperatorAdded(
address indexed _operator
);
event OperatorRemoved(
address indexed _operator
);
event AllowedAssetAdded(
address indexed _asset
);
event AllowedAssetRemoved(
address indexed _asset
);
event UseAssetAllowlistUpdated(
bool _status
);
event OwnerFeeSplitUpdated(
uint256 _newFeeSplit
);
event OwnerFeeRecipientUpdated(
address indexed _newFeeRecipient
);
/* ============ Modifiers ============ */
/**
* Throws if the sender is not the SetToken methodologist
*/
modifier onlyMethodologist() {
require(msg.sender == methodologist, "Must be methodologist");
_;
}
/**
* Throws if the sender is not an initialized extension
*/
modifier onlyExtension() {
require(extensionAllowlist[msg.sender] == ExtensionState.INITIALIZED, "Must be initialized extension");
_;
}
/* ============ State Variables ============ */
// Instance of SetToken
ISetToken public immutable setToken;
// Address of factory contract used to deploy contract
address public immutable factory;
// Mapping to check which ExtensionState a given extension is in
mapping(address => ExtensionState) public extensionAllowlist;
// Array of initialized extensions
address[] internal extensions;
// Mapping indicating if address is an approved operator
mapping(address=>bool) public operatorAllowlist;
// List of approved operators
address[] internal operators;
// Mapping indicating if asset is approved to be traded for, wrapped into, claimed, etc.
mapping(address=>bool) public assetAllowlist;
// List of allowed assets
address[] internal allowedAssets;
// Toggle if asset allow list is being enforced
bool public useAssetAllowlist;
// Global owner fee split that can be referenced by Extensions
uint256 public ownerFeeSplit;
// Address owners portions of fees get sent to
address public ownerFeeRecipient;
// Address of methodologist which serves as providing methodology for the index and receives fee splits
address public methodologist;
/* ============ Constructor ============ */
constructor(
ISetToken _setToken,
address _factory,
address _methodologist,
address[] memory _extensions,
address[] memory _operators,
address[] memory _allowedAssets,
bool _useAssetAllowlist
)
public
{
setToken = _setToken;
factory = _factory;
methodologist = _methodologist;
useAssetAllowlist = _useAssetAllowlist;
emit UseAssetAllowlistUpdated(_useAssetAllowlist);
_addExtensions(_extensions);
_addOperators(_operators);
_addAllowedAssets(_allowedAssets);
}
/* ============ External Functions ============ */
/**
* ONLY EXTENSION: Interact with a module registered on the SetToken. In order to ensure SetToken admin
* functions can only be changed from this contract no calls to the SetToken can originate from Extensions.
* To transfer SetTokens use the `transferTokens` function.
*
* @param _module Module to interact with
* @param _data Byte data of function to call in module
*/
function interactManager(address _module, bytes calldata _data) external onlyExtension {
require(_module != address(setToken), "Extensions cannot call SetToken");
// Invoke call to module, assume value will always be 0
_module.functionCallWithValue(_data, 0);
}
/**
* EXTENSION ONLY: Transfers _tokens held by the manager to _destination. Can be used to
* distribute fees or recover anything sent here accidentally.
*
* @param _token ERC20 token to send
* @param _destination Address receiving the tokens
* @param _amount Quantity of tokens to send
*/
function transferTokens(address _token, address _destination, uint256 _amount) external onlyExtension {
IERC20(_token).safeTransfer(_destination, _amount);
}
/**
* Initializes an added extension from PENDING to INITIALIZED state and adds to extension array. An
* address can only enter a PENDING state if it is an enabled extension added by the manager. Only
* callable by the extension itself, hence msg.sender is the subject of update.
*/
function initializeExtension() external {
require(extensionAllowlist[msg.sender] == ExtensionState.PENDING, "Extension must be pending");
extensionAllowlist[msg.sender] = ExtensionState.INITIALIZED;
extensions.push(msg.sender);
emit ExtensionInitialized(msg.sender);
}
/**
* ONLY OWNER: Add new extension(s) that the DelegatedManager can call. Puts extensions into PENDING
* state, each must be initialized in order to be used.
*
* @param _extensions New extension(s) to add
*/
function addExtensions(address[] memory _extensions) external onlyOwner {
_addExtensions(_extensions);
}
/**
* ONLY OWNER: Remove existing extension(s) tracked by the DelegatedManager. Removed extensions are
* placed in NONE state.
*
* @param _extensions Old extension to remove
*/
function removeExtensions(address[] memory _extensions) external onlyOwner {
for (uint256 i = 0; i < _extensions.length; i++) {
address extension = _extensions[i];
require(extensionAllowlist[extension] == ExtensionState.INITIALIZED, "Extension not initialized");
extensions.removeStorage(extension);
extensionAllowlist[extension] = ExtensionState.NONE;
IGlobalExtension(extension).removeExtension();
emit ExtensionRemoved(extension);
}
}
/**
* ONLY OWNER: Add new operator(s) address(es)
*
* @param _operators New operator(s) to add
*/
function addOperators(address[] memory _operators) external onlyOwner {
_addOperators(_operators);
}
/**
* ONLY OWNER: Remove operator(s) from the allowlist
*
* @param _operators New operator(s) to remove
*/
function removeOperators(address[] memory _operators) external onlyOwner {
for (uint256 i = 0; i < _operators.length; i++) {
address operator = _operators[i];
require(operatorAllowlist[operator], "Operator not already added");
operators.removeStorage(operator);
operatorAllowlist[operator] = false;
emit OperatorRemoved(operator);
}
}
/**
* ONLY OWNER: Add new asset(s) that can be traded to, wrapped to, or claimed
*
* @param _assets New asset(s) to add
*/
function addAllowedAssets(address[] memory _assets) external onlyOwner {
_addAllowedAssets(_assets);
}
/**
* ONLY OWNER: Remove asset(s) so that it/they can't be traded to, wrapped to, or claimed
*
* @param _assets Asset(s) to remove
*/
function removeAllowedAssets(address[] memory _assets) external onlyOwner {
for (uint256 i = 0; i < _assets.length; i++) {
address asset = _assets[i];
require(assetAllowlist[asset], "Asset not already added");
allowedAssets.removeStorage(asset);
assetAllowlist[asset] = false;
emit AllowedAssetRemoved(asset);
}
}
/**
* ONLY OWNER: Toggle useAssetAllowlist on and off. When false asset allowlist is ignored
* when true it is enforced.
*
* @param _useAssetAllowlist Bool indicating whether to use asset allow list
*/
function updateUseAssetAllowlist(bool _useAssetAllowlist) external onlyOwner {
useAssetAllowlist = _useAssetAllowlist;
emit UseAssetAllowlistUpdated(_useAssetAllowlist);
}
/**
* MUTUAL UPGRADE: Update percent of fees that are sent to owner. Owner and Methodologist must each call this function to execute
* the update. If Owner and Methodologist point to the same address, the update can be executed in a single call.
*
* @param _newFeeSplit Percent in precise units (100% = 10**18) of fees that accrue to owner
*/
function updateOwnerFeeSplit(uint256 _newFeeSplit) external mutualUpgrade(owner(), methodologist) {
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Invalid fee split");
ownerFeeSplit = _newFeeSplit;
emit OwnerFeeSplitUpdated(_newFeeSplit);
}
/**
* ONLY OWNER: Update address owner receives fees at
*
* @param _newFeeRecipient Address to send owner fees to
*/
function updateOwnerFeeRecipient(address _newFeeRecipient) external onlyOwner {
require(_newFeeRecipient != address(0), "Null address passed");
ownerFeeRecipient = _newFeeRecipient;
emit OwnerFeeRecipientUpdated(_newFeeRecipient);
}
/**
* ONLY METHODOLOGIST: Update the methodologist address
*
* @param _newMethodologist New methodologist address
*/
function setMethodologist(address _newMethodologist) external onlyMethodologist {
require(_newMethodologist != address(0), "Null address passed");
methodologist = _newMethodologist;
emit MethodologistChanged(_newMethodologist);
}
/**
* ONLY OWNER: Update the SetToken manager address.
*
* @param _newManager New manager address
*/
function setManager(address _newManager) external onlyOwner {
require(_newManager != address(0), "Zero address not valid");
require(extensions.length == 0, "Must remove all extensions");
setToken.setManager(_newManager);
}
/**
* ONLY OWNER: Add a new module to the SetToken.
*
* @param _module New module to add
*/
function addModule(address _module) external onlyOwner {
setToken.addModule(_module);
}
/**
* ONLY OWNER: Remove a module from the SetToken.
*
* @param _module Module to remove
*/
function removeModule(address _module) external onlyOwner {
setToken.removeModule(_module);
}
/* ============ External View Functions ============ */
function isAllowedAsset(address _asset) external view returns(bool) {
return !useAssetAllowlist || assetAllowlist[_asset];
}
function isPendingExtension(address _extension) external view returns(bool) {
return extensionAllowlist[_extension] == ExtensionState.PENDING;
}
function isInitializedExtension(address _extension) external view returns(bool) {
return extensionAllowlist[_extension] == ExtensionState.INITIALIZED;
}
function getExtensions() external view returns(address[] memory) {
return extensions;
}
function getOperators() external view returns(address[] memory) {
return operators;
}
function getAllowedAssets() external view returns(address[] memory) {
return allowedAssets;
}
/* ============ Internal Functions ============ */
/**
* Add extensions that the DelegatedManager can call.
*
* @param _extensions New extension to add
*/
function _addExtensions(address[] memory _extensions) internal {
for (uint256 i = 0; i < _extensions.length; i++) {
address extension = _extensions[i];
require(extensionAllowlist[extension] == ExtensionState.NONE , "Extension already exists");
extensionAllowlist[extension] = ExtensionState.PENDING;
emit ExtensionAdded(extension);
}
}
/**
* Add new operator(s) address(es)
*
* @param _operators New operator to add
*/
function _addOperators(address[] memory _operators) internal {
for (uint256 i = 0; i < _operators.length; i++) {
address operator = _operators[i];
require(!operatorAllowlist[operator], "Operator already added");
operators.push(operator);
operatorAllowlist[operator] = true;
emit OperatorAdded(operator);
}
}
/**
* Add new assets that can be traded to, wrapped to, or claimed
*
* @param _assets New asset to add
*/
function _addAllowedAssets(address[] memory _assets) internal {
for (uint256 i = 0; i < _assets.length; i++) {
address asset = _assets[i];
require(!assetAllowlist[asset], "Asset already added");
allowedAssets.push(asset);
assetAllowlist[asset] = true;
emit AllowedAssetAdded(asset);
}
}
}
ICManager.sol 321 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity ^0.6.10;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IIndexModule } from "../interfaces/IIndexModule.sol";
import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol";
import { MutualUpgrade } from "../lib/MutualUpgrade.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { TimeLockUpgrade } from "../lib/TimeLockUpgrade.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
contract ICManager is TimeLockUpgrade, MutualUpgrade {
using Address for address;
using SafeMath for uint256;
using PreciseUnitMath for uint256;
/* ============ Events ============ */
event FeesAccrued(
uint256 _totalFees,
uint256 _operatorTake,
uint256 _methodologistTake
);
/* ============ Modifiers ============ */
/**
* Throws if the sender is not the SetToken operator
*/
modifier onlyOperator() {
require(msg.sender == operator, "Must be operator");
_;
}
/**
* Throws if the sender is not the SetToken methodologist
*/
modifier onlyMethodologist() {
require(msg.sender == methodologist, "Must be methodologist");
_;
}
/* ============ State Variables ============ */
// Instance of SetToken
ISetToken public setToken;
// Address of IndexModule for managing rebalances
IIndexModule public indexModule;
// Address of StreamingFeeModule
IStreamingFeeModule public feeModule;
// Address of operator
address public operator;
// Address of methodologist
address public methodologist;
// Percent in 1e18 of streamingFees sent to operator
uint256 public operatorFeeSplit;
/* ============ Constructor ============ */
constructor(
ISetToken _setToken,
IIndexModule _indexModule,
IStreamingFeeModule _feeModule,
address _operator,
address _methodologist,
uint256 _operatorFeeSplit
)
public
{
require(
_operatorFeeSplit <= PreciseUnitMath.preciseUnit(),
"Operator Fee Split must be less than 1e18"
);
setToken = _setToken;
indexModule = _indexModule;
feeModule = _feeModule;
operator = _operator;
methodologist = _methodologist;
operatorFeeSplit = _operatorFeeSplit;
}
/* ============ External Functions ============ */
/**
* OPERATOR ONLY: Start rebalance in IndexModule. Set new target units, zeroing out any units for components being removed from index.
* Log position multiplier to adjust target units in case fees are accrued.
*
* @param _newComponents Array of new components to add to allocation
* @param _newComponentsTargetUnits Array of target units at end of rebalance for new components, maps to same index of component
* @param _oldComponentsTargetUnits Array of target units at end of rebalance for old component, maps to same index of component,
* if component being removed set to 0.
* @param _positionMultiplier Position multiplier when target units were calculated, needed in order to adjust target units
* if fees accrued
*/
function startRebalance(
address[] calldata _newComponents,
uint256[] calldata _newComponentsTargetUnits,
uint256[] calldata _oldComponentsTargetUnits,
uint256 _positionMultiplier
)
external
onlyOperator
{
indexModule.startRebalance(_newComponents, _newComponentsTargetUnits, _oldComponentsTargetUnits, _positionMultiplier);
}
/**
* OPERATOR ONLY: Set trade maximums for passed components
*
* @param _components Array of components
* @param _tradeMaximums Array of trade maximums mapping to correct component
*/
function setTradeMaximums(
address[] calldata _components,
uint256[] calldata _tradeMaximums
)
external
onlyOperator
{
indexModule.setTradeMaximums(_components, _tradeMaximums);
}
/**
* OPERATOR ONLY: Set exchange for passed components
*
* @param _components Array of components
* @param _exchanges Array of exchanges mapping to correct component, uint256 used to signify exchange
*/
function setAssetExchanges(
address[] calldata _components,
uint256[] calldata _exchanges
)
external
onlyOperator
{
indexModule.setExchanges(_components, _exchanges);
}
/**
* OPERATOR ONLY: Set exchange for passed components
*
* @param _components Array of components
* @param _coolOffPeriods Array of cool off periods to correct component
*/
function setCoolOffPeriods(
address[] calldata _components,
uint256[] calldata _coolOffPeriods
)
external
onlyOperator
{
indexModule.setCoolOffPeriods(_components, _coolOffPeriods);
}
/**
* OPERATOR ONLY: Toggle ability for passed addresses to trade from current state
*
* @param _traders Array trader addresses to toggle status
* @param _statuses Booleans indicating if matching trader can trade
*/
function updateTraderStatus(
address[] calldata _traders,
bool[] calldata _statuses
)
external
onlyOperator
{
indexModule.updateTraderStatus(_traders, _statuses);
}
/**
* OPERATOR ONLY: Toggle whether anyone can trade, bypassing the traderAllowList
*
* @param _status Boolean indicating if anyone can trade
*/
function updateAnyoneTrade(bool _status) external onlyOperator {
indexModule.updateAnyoneTrade(_status);
}
/**
* Accrue fees from streaming fee module and transfer tokens to operator / methodologist addresses based on fee split
*/
function accrueFeeAndDistribute() public {
feeModule.accrueFee(setToken);
uint256 setTokenBalance = setToken.balanceOf(address(this));
uint256 operatorTake = setTokenBalance.preciseMul(operatorFeeSplit);
uint256 methodologistTake = setTokenBalance.sub(operatorTake);
setToken.transfer(operator, operatorTake);
setToken.transfer(methodologist, methodologistTake);
emit FeesAccrued(setTokenBalance, operatorTake, methodologistTake);
}
/**
* OPERATOR OR METHODOLOGIST ONLY: Update the SetToken manager address. Operator and Methodologist must each call
* this function to execute the update.
*
* @param _newManager New manager address
*/
function updateManager(address _newManager) external mutualUpgrade(operator, methodologist) {
setToken.setManager(_newManager);
}
/**
* OPERATOR ONLY: Add a new module to the SetToken.
*
* @param _module New module to add
*/
function addModule(address _module) external onlyOperator {
setToken.addModule(_module);
}
/**
* OPERATOR ONLY: Interact with a module registered on the SetToken. Cannot be used to call functions in the
* fee module, due to ability to bypass methodologist permissions to update streaming fee.
*
* @param _module Module to interact with
* @param _data Byte data of function to call in module
*/
function interactModule(address _module, bytes calldata _data) external onlyOperator {
require(_module != address(feeModule), "Must not be fee module");
// Invoke call to module, assume value will always be 0
_module.functionCallWithValue(_data, 0);
}
/**
* OPERATOR ONLY: Remove a new module from the SetToken.
*
* @param _module Module to remove
*/
function removeModule(address _module) external onlyOperator {
setToken.removeModule(_module);
}
/**
* METHODOLOGIST ONLY: Update the streaming fee for the SetToken. Subject to timelock period agreed upon by the
* operator and methodologist
*
* @param _newFee New streaming fee percentage
*/
function updateStreamingFee(uint256 _newFee) external timeLockUpgrade onlyMethodologist {
feeModule.updateStreamingFee(setToken, _newFee);
}
/**
* OPERATOR OR METHODOLOGIST ONLY: Update the fee recipient address. Operator and Methodologist must each call
* this function to execute the update.
*
* @param _newFeeRecipient New fee recipient address
*/
function updateFeeRecipient(address _newFeeRecipient) external mutualUpgrade(operator, methodologist) {
feeModule.updateFeeRecipient(setToken, _newFeeRecipient);
}
/**
* OPERATOR OR METHODOLOGIST ONLY: Update the fee split percentage. Operator and Methodologist must each call
* this function to execute the update.
*
* @param _newFeeSplit New fee split percentage
*/
function updateFeeSplit(uint256 _newFeeSplit) external mutualUpgrade(operator, methodologist) {
require(
_newFeeSplit <= PreciseUnitMath.preciseUnit(),
"Operator Fee Split must be less than 1e18"
);
// Accrue fee to operator and methodologist prior to new fee split
accrueFeeAndDistribute();
operatorFeeSplit = _newFeeSplit;
}
/**
* OPERATOR ONLY: Update the index module
*
* @param _newIndexModule New index module
*/
function updateIndexModule(IIndexModule _newIndexModule) external onlyOperator {
indexModule = _newIndexModule;
}
/**
* METHODOLOGIST ONLY: Update the methodologist address
*
* @param _newMethodologist New methodologist address
*/
function updateMethodologist(address _newMethodologist) external onlyMethodologist {
methodologist = _newMethodologist;
}
/**
* OPERATOR ONLY: Update the operator address
*
* @param _newOperator New operator address
*/
function updateOperator(address _newOperator) external onlyOperator {
operator = _newOperator;
}
/**
* OPERATOR OR METHODOLOGIST ONLY: Update the timelock period for updating the streaming fee percentage.
* Operator and Methodologist must each call this function to execute the update.
*
* @param _newTimeLockPeriod New timelock period in seconds
*/
function setTimeLockPeriod(uint256 _newTimeLockPeriod) external override mutualUpgrade(operator, methodologist) {
timeLockPeriod = _newTimeLockPeriod;
}
}
ManagerCore.sol 236 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { AddressArrayUtils } from "./lib/AddressArrayUtils.sol";
/**
* @title ManagerCore
* @author Set Protocol
*
* Registry for governance approved GlobalExtensions, DelegatedManagerFactories, and DelegatedManagers.
*/
contract ManagerCore is Ownable {
using AddressArrayUtils for address[];
/* ============ Events ============ */
event ExtensionAdded(address indexed _extension);
event ExtensionRemoved(address indexed _extension);
event FactoryAdded(address indexed _factory);
event FactoryRemoved(address indexed _factory);
event ManagerAdded(address indexed _manager, address indexed _factory);
event ManagerRemoved(address indexed _manager);
/* ============ Modifiers ============ */
/**
* Throws if function is called by any address other than a valid factory.
*/
modifier onlyFactory() {
require(isFactory[msg.sender], "Only valid factories can call");
_;
}
modifier onlyInitialized() {
require(isInitialized, "Contract must be initialized.");
_;
}
/* ============ State Variables ============ */
// List of enabled extensions
address[] public extensions;
// List of enabled factories of managers
address[] public factories;
// List of enabled managers
address[] public managers;
// Mapping to check whether address is valid Extension, Factory, or Manager
mapping(address => bool) public isExtension;
mapping(address => bool) public isFactory;
mapping(address => bool) public isManager;
// Return true if the ManagerCore is initialized
bool public isInitialized;
/* ============ External Functions ============ */
/**
* Initializes any predeployed factories. Note: This function can only be called by
* the owner once to batch initialize the initial system contracts.
*
* @param _extensions List of extensions to add
* @param _factories List of factories to add
*/
function initialize(
address[] memory _extensions,
address[] memory _factories
)
external
onlyOwner
{
require(!isInitialized, "ManagerCore is already initialized");
extensions = _extensions;
factories = _factories;
// Loop through and initialize isExtension and isFactory mapping
for (uint256 i = 0; i < _extensions.length; i++) {
_addExtension(_extensions[i]);
}
for (uint256 i = 0; i < _factories.length; i++) {
_addFactory(_factories[i]);
}
// Set to true to only allow initialization once
isInitialized = true;
}
/**
* PRIVILEGED GOVERNANCE FUNCTION. Allows governance to add an extension
*
* @param _extension Address of the extension contract to add
*/
function addExtension(address _extension) external onlyInitialized onlyOwner {
require(!isExtension[_extension], "Extension already exists");
_addExtension(_extension);
extensions.push(_extension);
}
/**
* PRIVILEGED GOVERNANCE FUNCTION. Allows governance to remove an extension
*
* @param _extension Address of the extension contract to remove
*/
function removeExtension(address _extension) external onlyInitialized onlyOwner {
require(isExtension[_extension], "Extension does not exist");
extensions.removeStorage(_extension);
isExtension[_extension] = false;
emit ExtensionRemoved(_extension);
}
/**
* PRIVILEGED GOVERNANCE FUNCTION. Allows governance to add a factory
*
* @param _factory Address of the factory contract to add
*/
function addFactory(address _factory) external onlyInitialized onlyOwner {
require(!isFactory[_factory], "Factory already exists");
_addFactory(_factory);
factories.push(_factory);
}
/**
* PRIVILEGED GOVERNANCE FUNCTION. Allows governance to remove a factory
*
* @param _factory Address of the factory contract to remove
*/
function removeFactory(address _factory) external onlyInitialized onlyOwner {
require(isFactory[_factory], "Factory does not exist");
factories.removeStorage(_factory);
isFactory[_factory] = false;
emit FactoryRemoved(_factory);
}
/**
* PRIVILEGED FACTORY FUNCTION. Adds a newly deployed manager as an enabled manager.
*
* @param _manager Address of the manager contract to add
*/
function addManager(address _manager) external onlyInitialized onlyFactory {
require(!isManager[_manager], "Manager already exists");
isManager[_manager] = true;
managers.push(_manager);
emit ManagerAdded(_manager, msg.sender);
}
/**
* PRIVILEGED GOVERNANCE FUNCTION. Allows governance to remove a manager
*
* @param _manager Address of the manager contract to remove
*/
function removeManager(address _manager) external onlyInitialized onlyOwner {
require(isManager[_manager], "Manager does not exist");
managers.removeStorage(_manager);
isManager[_manager] = false;
emit ManagerRemoved(_manager);
}
/* ============ External Getter Functions ============ */
function getExtensions() external view returns (address[] memory) {
return extensions;
}
function getFactories() external view returns (address[] memory) {
return factories;
}
function getManagers() external view returns (address[] memory) {
return managers;
}
/* ============ Internal Functions ============ */
/**
* Add an extension tracked on the ManagerCore
*
* @param _extension Address of the extension contract to add
*/
function _addExtension(address _extension) internal {
require(_extension != address(0), "Zero address submitted.");
isExtension[_extension] = true;
emit ExtensionAdded(_extension);
}
/**
* Add a factory tracked on the ManagerCore
*
* @param _factory Address of the factory contract to add
*/
function _addFactory(address _factory) internal {
require(_factory != address(0), "Zero address submitted.");
isFactory[_factory] = true;
emit FactoryAdded(_factory);
}
}
BaseExtensionMock.sol 61 lines
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { BaseExtension } from "../lib/BaseExtension.sol";
import { IBaseManager } from "../interfaces/IBaseManager.sol";
contract BaseExtensionMock is BaseExtension {
constructor(IBaseManager _manager) public BaseExtension(_manager) {}
/* ============ External Functions ============ */
function testInvokeManagerTransfer(address _token, address _destination, uint256 _amount) external {
invokeManagerTransfer(_token, _destination, _amount);
}
function testInvokeManager(address _module, bytes calldata _encoded) external {
invokeManager(_module, _encoded);
}
function testOnlyOperator()
external
onlyOperator
{}
function testOnlyMethodologist()
external
onlyMethodologist
{}
function testOnlyEOA()
external
onlyEOA
{}
function testOnlyAllowedCaller(address _caller)
external
onlyAllowedCaller(_caller)
{}
function interactManager(address _target, bytes calldata _data) external {
invokeManager(_target, _data);
}
}
BaseGlobalExtensionMock.sol 110 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "../interfaces/ISetToken.sol";
import { BaseGlobalExtension } from "../lib/BaseGlobalExtension.sol";
import { IDelegatedManager } from "../interfaces/IDelegatedManager.sol";
import { IManagerCore } from "../interfaces/IManagerCore.sol";
import { ModuleMock } from "./ModuleMock.sol";
contract BaseGlobalExtensionMock is BaseGlobalExtension {
/* ============ State Variables ============ */
ModuleMock public immutable module;
/* ============ Constructor ============ */
constructor(
IManagerCore _managerCore,
ModuleMock _module
)
public
BaseGlobalExtension(_managerCore)
{
module = _module;
}
/* ============ External Functions ============ */
function initializeExtension(
IDelegatedManager _delegatedManager
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
_initializeExtension(_delegatedManager.setToken(), _delegatedManager);
}
function initializeModuleAndExtension(
IDelegatedManager _delegatedManager
)
external
onlyOwnerAndValidManager(_delegatedManager)
{
require(_delegatedManager.isPendingExtension(address(this)), "Extension must be pending");
ISetToken setToken = _delegatedManager.setToken();
_initializeExtension(setToken, _delegatedManager);
bytes memory callData = abi.encodeWithSignature("initialize(address)", setToken);
_invokeManager(_delegatedManager, address(module), callData);
}
function testInvokeManager(ISetToken _setToken, address _module, bytes calldata _encoded) external {
_invokeManager(_manager(_setToken), _module, _encoded);
}
function testOnlyOwner(ISetToken _setToken)
external
onlyOwner(_setToken)
{}
function testOnlyMethodologist(ISetToken _setToken)
external
onlyMethodologist(_setToken)
{}
function testOnlyOperator(ISetToken _setToken)
external
onlyOperator(_setToken)
{}
function testOnlyOwnerAndValidManager(IDelegatedManager _delegatedManager)
external
onlyOwnerAndValidManager(_delegatedManager)
{}
function testOnlyAllowedAsset(ISetToken _setToken, address _asset)
external
onlyAllowedAsset(_setToken, _asset)
{}
function removeExtension() external override {
IDelegatedManager delegatedManager = IDelegatedManager(msg.sender);
ISetToken setToken = delegatedManager.setToken();
_removeExtension(setToken, delegatedManager);
}
}
BatchTradeAdapterMock.sol 86 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* Trade Adapter that doubles as a mock exchange
*/
contract BatchTradeAdapterMock {
/* ============ Helper Functions ============ */
function withdraw(address _token)
external
{
uint256 balance = ERC20(_token).balanceOf(address(this));
require(ERC20(_token).transfer(msg.sender, balance), "ERC20 transfer failed");
}
/* ============ Trade Functions ============ */
function trade(
address _sourceToken,
address _destinationToken,
address _destinationAddress,
uint256 _sourceQuantity,
uint256 _minDestinationQuantity
)
external
{
uint256 destinationBalance = ERC20(_destinationToken).balanceOf(address(this));
require(ERC20(_sourceToken).transferFrom(_destinationAddress, address(this), _sourceQuantity), "ERC20 TransferFrom failed");
if (_minDestinationQuantity == 1) { // byte revert case, min nonzero uint256 minimum receive quantity
bytes memory data = abi.encodeWithSelector(
bytes4(keccak256("trade(address,address,address,uint256,uint256)")),
_sourceToken,
_destinationToken,
_destinationAddress,
_sourceQuantity,
_minDestinationQuantity
);
assembly { revert(add(data, 32), mload(data)) }
}
if (destinationBalance >= _minDestinationQuantity) { // normal case
require(ERC20(_destinationToken).transfer(_destinationAddress, destinationBalance), "ERC20 transfer failed");
}
else { // string revert case, minimum destination quantity not in exchange
revert("Insufficient funds in exchange");
}
}
/* ============ Adapter Functions ============ */
function getSpender()
external
view
returns (address)
{
return address(this);
}
function getTradeCalldata(
address _sourceToken,
address _destinationToken,
address _destinationAddress,
uint256 _sourceQuantity,
uint256 _minDestinationQuantity,
bytes memory /* _data */
)
external
view
returns (address, uint256, bytes memory)
{
// Encode method data for SetToken to invoke
bytes memory methodData = abi.encodeWithSignature(
"trade(address,address,address,uint256,uint256)",
_sourceToken,
_destinationToken,
_destinationAddress,
_sourceQuantity,
_minDestinationQuantity
);
return (address(this), 0, methodData);
}
}
FlashMintLeveragedCompMock.sol 107 lines
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import {
FlashMintLeveragedForCompound,
ISetToken,
DEXAdapter,
IController,
IDebtIssuanceModule,
ICompoundLeverageModule,
IERC20
} from "../exchangeIssuance/FlashMintLeveragedForCompound.sol";
contract FlashMintLeveragedCompMock is FlashMintLeveragedForCompound {
constructor(
DEXAdapter.Addresses memory _dexAddresses,
IController _setController,
IDebtIssuanceModule _debtIssuanceModule,
ICompoundLeverageModule _compoundLeverageModule,
address _aaveAddressProvider,
address _cEther
) public
FlashMintLeveragedForCompound(_dexAddresses, _setController,_debtIssuanceModule, _compoundLeverageModule, _aaveAddressProvider, _cEther )
{}
function liquidateCollateralTokens(
uint256 _collateralTokenSpent,
ISetToken _setToken,
uint256 _setAmount,
address _originalSender,
address _outputToken,
uint256 _minAmountOutputToken,
address _collateralToken,
uint256 _collateralAmount,
DEXAdapter.SwapData memory _swapData
) external {
_liquidateCollateralTokens(
_collateralTokenSpent,
_setToken,
_setAmount,
_originalSender,
_outputToken,
_minAmountOutputToken,
_collateralToken,
_collateralAmount,
_swapData
);
}
function liquidateCollateralTokensForETH(
address _collateralToken,
uint256 _collateralRemaining,
address _originalSender,
uint256 _minAmountOutputToken,
DEXAdapter.SwapData memory _swapData
)
external
returns(uint256)
{
return _liquidateCollateralTokensForETH(
_collateralToken,
_collateralRemaining,
_originalSender,
_minAmountOutputToken,
_swapData
);
}
function transferShortfallFromSender(
address _token,
uint256 _shortfall,
address _originalSender
)
external
{
_transferShortfallFromSender(
_token,
_shortfall,
_originalSender
);
}
function makeUpShortfallWithERC20(
address _collateralToken,
uint256 _collateralTokenShortfall,
address _originalSender,
IERC20 _inputToken,
uint256 _maxAmountInputToken,
DEXAdapter.SwapData memory _swapData
)
external
returns (uint256)
{
return _makeUpShortfallWithERC20(
_collateralToken,
_collateralTokenShortfall,
_originalSender,
_inputToken,
_maxAmountInputToken,
_swapData
);
}
}
FlexibleLeverageStrategyExtensionMock.sol 92 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { IBaseManager } from "../interfaces/IBaseManager.sol";
import { BaseExtension } from "../lib/BaseExtension.sol";
contract FlexibleLeverageStrategyExtensionMock is BaseExtension {
/* ============ Enums ============ */
enum ShouldRebalance {
NONE, // Indicates no rebalance action can be taken
REBALANCE, // Indicates rebalance() function can be successfully called
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
RIPCORD // Indicates ripcord() function can be successfully called
}
/* ============ State Variables ============ */
uint256 public currentLeverageRatio; // The current leverage ratio
string public exchangeName; // The exchange name
/* ============ Events ============ */
event RebalanceEvent(ShouldRebalance _rebalance); // A rebalance occurred
/**
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
*
* @param _manager Address of IBaseManager contract
*/
constructor(IBaseManager _manager, uint256 _currentLeverageRatio, string memory _exchangeName) public BaseExtension(_manager) {
currentLeverageRatio = _currentLeverageRatio;
exchangeName = _exchangeName;
}
/**
* Helper that checks if conditions are met for rebalance or ripcord. Returns an enum with 0 = no rebalance, 1 = call rebalance(), 2 = call iterateRebalance()
* 3 = call ripcord()
*
* @return (string[] memory, ShouldRebalance[] memory) List of exchange names and a list of enums representing whether that exchange should rebalance
*/
function shouldRebalance() external view returns (string[] memory, ShouldRebalance[] memory) {
return _shouldRebalance();
}
function shouldRebalanceWithBounds(uint256 _customMinLeverageRatio, uint256 _customMaxLeverageRatio) external view returns (string[] memory, ShouldRebalance[] memory) {
return _shouldRebalance();
}
/**
* @param _exchangeName the exchange used for trading
*/
function rebalance(string memory _exchangeName) external onlyAllowedCaller(msg.sender) {
require(keccak256(abi.encodePacked(_exchangeName)) == keccak256(abi.encodePacked(exchangeName)), "Exchange names are not equal");
emit RebalanceEvent(ShouldRebalance.REBALANCE);
}
/**
* @param _exchangeName the exchange used for trading
*/
function iterateRebalance(string memory _exchangeName) external onlyAllowedCaller(msg.sender) {
require(keccak256(abi.encodePacked(_exchangeName)) == keccak256(abi.encodePacked(exchangeName)), "Exchange names are not equal");
emit RebalanceEvent(ShouldRebalance.ITERATE_REBALANCE);
}
/**
*
* @param _exchangeName the exchange used for trading
*/
function ripcord(string memory _exchangeName) external {
require(keccak256(abi.encodePacked(_exchangeName)) == keccak256(abi.encodePacked(exchangeName)), "Exchange names are not equal");
emit RebalanceEvent(ShouldRebalance.RIPCORD);
}
function _shouldRebalance() private view returns (string[] memory, ShouldRebalance[] memory) {
ShouldRebalance rebalanceStrategy = ShouldRebalance.NONE;
if (currentLeverageRatio == 1) {
rebalanceStrategy = ShouldRebalance.REBALANCE;
} else if (currentLeverageRatio == 2) {
rebalanceStrategy = ShouldRebalance.ITERATE_REBALANCE;
} else if (currentLeverageRatio == 3) {
rebalanceStrategy = ShouldRebalance.RIPCORD;
}
string[] memory exchangeNames = new string[](1);
exchangeNames[0] = exchangeName;
ShouldRebalance[] memory shouldRebalances = new ShouldRebalance[](1);
shouldRebalances[0] = rebalanceStrategy;
return (exchangeNames, shouldRebalances);
}
}
FLIStrategyExtensionMock.sol 88 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { FlexibleLeverageStrategyExtension } from "../adapters/FlexibleLeverageStrategyExtension.sol";
// Mock contract for FlexibleLeverageStrategyExtension used to test FLIRebalanceViewer
contract FLIStrategyExtensionMock {
string[] internal shouldRebalanceNames;
FlexibleLeverageStrategyExtension.ShouldRebalance[] internal shouldRebalancesEnums;
uint256[] internal chunkRebalanceSizes;
address internal chunkRebalanceSellAsset;
address internal chunkRebalanceBuyAsset;
FlexibleLeverageStrategyExtension.ContractSettings internal strategy;
mapping(string => FlexibleLeverageStrategyExtension.ExchangeSettings) internal exchangeSettings;
function shouldRebalanceWithBounds(
uint256 /* _customMinLeverageRatio */,
uint256 /* _customMaxLeverageRatio */
)
external
view
returns(string[] memory, FlexibleLeverageStrategyExtension.ShouldRebalance[] memory)
{
return (shouldRebalanceNames, shouldRebalancesEnums);
}
function getChunkRebalanceNotional(
string[] calldata /* _exchangeNames */
)
external
view
returns(uint256[] memory sizes, address sellAsset, address buyAsset)
{
sizes = chunkRebalanceSizes;
sellAsset = chunkRebalanceSellAsset;
buyAsset = chunkRebalanceBuyAsset;
}
function getStrategy() external view returns (FlexibleLeverageStrategyExtension.ContractSettings memory) {
return strategy;
}
function getExchangeSettings(string memory _exchangeName) external view returns (FlexibleLeverageStrategyExtension.ExchangeSettings memory) {
return exchangeSettings[_exchangeName];
}
function getEnabledExchanges() external view returns (string[] memory) {
return shouldRebalanceNames;
}
/* =========== Functions for setting mock state =========== */
function setShouldRebalanceWithBounds(
string[] memory _shouldRebalanceNames,
FlexibleLeverageStrategyExtension.ShouldRebalance[] memory _shouldRebalancesEnums
)
external
{
shouldRebalanceNames = _shouldRebalanceNames;
shouldRebalancesEnums = _shouldRebalancesEnums;
}
function setGetChunkRebalanceWithBounds(
uint256[] memory _chunkRebalanceSizes,
address _chunkRebalanceSellAsset,
address _chunkRebalanceBuyAsset
)
external
{
chunkRebalanceSizes = _chunkRebalanceSizes;
chunkRebalanceSellAsset = _chunkRebalanceSellAsset;
chunkRebalanceBuyAsset = _chunkRebalanceBuyAsset;
}
function setStrategy(FlexibleLeverageStrategyExtension.ContractSettings memory _strategy) external {
strategy = _strategy;
}
function setExchangeSettings(string memory _exchangeName, FlexibleLeverageStrategyExtension.ExchangeSettings memory _settings) external {
exchangeSettings[_exchangeName] = _settings;
}
}
ManagerMock.sol 42 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ISetToken } from "../interfaces/ISetToken.sol";
import { IGlobalExtension } from "../interfaces/IGlobalExtension.sol";
contract ManagerMock {
ISetToken public immutable setToken;
constructor(
ISetToken _setToken
)
public
{
setToken = _setToken;
}
function removeExtensions(address[] memory _extensions) external {
for (uint256 i = 0; i < _extensions.length; i++) {
address extension = _extensions[i];
IGlobalExtension(extension).removeExtension();
}
}
}
ModuleMock.sol 48 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { IController } from "../interfaces/IController.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ModuleBase } from "../lib/ModuleBase.sol";
contract ModuleMock is ModuleBase {
bool public removed;
/* ============ Constructor ============ */
constructor(IController _controller) public ModuleBase(_controller) {}
/* ============ External Functions ============ */
function initialize(
ISetToken _setToken
)
external
onlyValidAndPendingSet(_setToken)
onlySetManager(_setToken, msg.sender)
{
_setToken.initializeModule();
}
function removeModule() external override {
removed = true;
}
}
MutualUpgradeMock.sol 28 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
import { MutualUpgrade } from "../lib/MutualUpgrade.sol";
// Mock contract implementation of MutualUpgrade functions
contract MutualUpgradeMock is
MutualUpgrade
{
uint256 public testUint;
address public owner;
address public methodologist;
constructor(address _owner, address _methodologist) public {
owner = _owner;
methodologist = _methodologist;
}
function testMutualUpgrade(
uint256 _testUint
)
external
mutualUpgrade(owner, methodologist)
{
testUint = _testUint;
}
}
MutualUpgradeV2Mock.sol 28 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
import { MutualUpgradeV2 } from "../lib/MutualUpgradeV2.sol";
// Mock contract implementation of MutualUpgradeV2 functions
contract MutualUpgradeV2Mock is
MutualUpgradeV2
{
uint256 public testUint;
address public owner;
address public methodologist;
constructor(address _owner, address _methodologist) public {
owner = _owner;
methodologist = _methodologist;
}
function testMutualUpgrade(
uint256 _testUint
)
external
mutualUpgrade(owner, methodologist)
{
testUint = _testUint;
}
}
NotionalTradeModuleMock.sol 86 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import {INotionalTradeModule} from "../interfaces/INotionalTradeModule.sol";
import {ISetToken} from "../interfaces/ISetToken.sol";
// mock class using BasicToken
contract NotionalTradeModuleMock is INotionalTradeModule{
function redeemMaturedPositions(ISetToken) external override {
}
function initialize(ISetToken) external override {
}
function updateAllowedSetToken(ISetToken, bool) external override {
}
function setRedeemToUnderlying(ISetToken, bool) external override {
}
function owner() external view override returns(address owner) {
}
function settleAccount(address) external override {
}
function getFCashComponents(ISetToken _setToken) external view override returns(address[] memory fCashComponents) {
}
function mintFixedFCashForToken(
ISetToken _setToken,
uint16 _currencyId,
uint40 _maturity,
uint256 _mintAmount,
address _sendToken,
uint256 _maxSendAmount
) external override returns(uint256 spentAmount) {
}
function redeemFixedFCashForToken(
ISetToken _setToken,
uint16 _currencyId,
uint40 _maturity,
uint256 _redeemAmount,
address _receiveToken,
uint256 _minReceiveAmount
) external override returns(uint256 receivedAmount) {
}
function mintFCashForFixedToken(
ISetToken _setToken,
uint16 _currencyId,
uint40 _maturity,
uint256 _minMintAmount,
address _sendToken,
uint256 _sendAmount
) external override returns(uint256) {}
function redeemFCashForFixedToken(
ISetToken _setToken,
uint16 _currencyId,
uint40 _maturity,
uint256 _maxRedeemAmount,
address _receiveToken,
uint256 _receiveAmount,
uint256 _maxReceiveAmountDeviation
) external override returns(uint256) {}
}
OptimisticOracleV3Mock.sol 107 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import {OptimisticOracleV3Interface} from "../interfaces/OptimisticOracleV3Interface.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface callbackInterface {
function assertionDisputedCallback(bytes32 assertionId) external;
function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) external;
}
/**
* @title Optimistic Oracle V3.
* @notice The OOv3 is used to assert truths about the world which are verified using an optimistic escalation game.
* @dev Core idea: an asserter makes a statement about a truth, calling "assertTruth". If this statement is not
* challenged, it is taken as the state of the world. If challenged, it is arbitrated using the UMA DVM, or if
* configured, an escalation manager. Escalation managers enable integrations to define their own security properties and
* tradeoffs, enabling the notion of "sovereign security".
*/
contract OptimisticOracleV3Mock is OptimisticOracleV3Interface {
address public asserter;
// Mock implementation of defaultIdentifier
function defaultIdentifier() public view override returns (bytes32) {
return (bytes32("helloWorld"));
}
function setAsserter(address _asserter) public {
asserter = _asserter;
}
// Mock implementation of getAssertion
function getAssertion(bytes32 ) public view override returns (Assertion memory) {
return (Assertion({
escalationManagerSettings: EscalationManagerSettings({
arbitrateViaEscalationManager: false,
discardOracle: false,
validateDisputers: false,
assertingCaller: address(0),
escalationManager: address(0)
}),
asserter: asserter,
assertionTime: uint64(0),
settled: false,
currency: IERC20(address(0)),
expirationTime: uint64(0),
settlementResolution: false,
domainId: bytes32(0),
identifier: bytes32(0),
bond: uint256(0),
callbackRecipient: address(0),
disputer: address(0)
}));
}
// Mock implementation of assertTruthWithDefaults
function assertTruthWithDefaults(bytes memory , address) public override returns (bytes32) {
return (bytes32(0));
}
// Mock implementation of assertTruth
function assertTruth(bytes memory, address , address , address , uint64 , IERC20 , uint256 , bytes32 , bytes32 ) public override returns (bytes32) {
return (bytes32("win"));
}
// Mock implementation of syncUmaParams
function syncUmaParams(bytes32 identifier, address currency) public override {
// No return for void functions
}
// Mock implementation of settleAssertion
function settleAssertion(bytes32 assertionId) public override {
// No return for void functions
}
// Mock implementation of settleAndGetAssertionResult
function settleAndGetAssertionResult(bytes32 ) public override returns (bool) {
return (false);
}
// Mock implementation of getAssertionResult
function getAssertionResult(bytes32 ) public view override returns (bool) {
return (false);
}
function disputeAssertion(bytes32 assertionId, address disputer) external override {
revert("Not implemented");
}
// Mock implementation of getMinimumBond
function getMinimumBond(address ) public view override returns (uint256) {
return (uint256(0));
}
// Mock calling a target contract's assertionDisputedCallback
function mockAssertionDisputedCallback(address target, bytes32 assertionId) public {
callbackInterface(target).assertionDisputedCallback(assertionId);
}
// Mock calling a target contract's assertionResolvedCallback
function mockAssertionResolvedCallback(address target, bytes32 assertionId, bool truthfully) public {
callbackInterface(target).assertionResolvedCallback(assertionId, truthfully);
}
}
PrtStakingPoolMock.sol 38 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity ^0.6.10;
pragma experimental ABIEncoderV2;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract PrtStakingPoolMock {
IERC20 public immutable rewardToken;
IERC20 public immutable stakeToken;
address public distributor;
constructor(IERC20 _rewardToken, IERC20 _stakeToken, address _distributor) public {
rewardToken = _rewardToken;
stakeToken = _stakeToken;
distributor = _distributor;
}
function accrue(uint256 _amount) external {
rewardToken.transferFrom(msg.sender, address(this), _amount);
}
}
StandardTokenMock.sol 38 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// mock class using BasicToken
contract StandardTokenMock is ERC20 {
constructor(
address _initialAccount,
uint256 _initialBalance,
string memory _name,
string memory _symbol,
uint8 _decimals
)
public
ERC20(_name, _symbol)
{
_mint(_initialAccount, _initialBalance);
_setupDecimals(_decimals);
}
}
StringArrayUtilsMock.sol 45 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { StringArrayUtils } from "../lib/StringArrayUtils.sol";
contract StringArrayUtilsMock {
using StringArrayUtils for string[];
string[] public storageArray;
function testIndexOf(string[] memory A, string memory a) external pure returns (uint256, bool) {
return A.indexOf(a);
}
function testRemoveStorage(string memory a) external {
storageArray.removeStorage(a);
}
function setStorageArray(string[] memory A) external {
storageArray = A;
}
function getStorageArray() external view returns(string[] memory) {
return storageArray;
}
}
TradeAdapterMock.sol 71 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* Trade Adapter that doubles as a mock exchange
*/
contract TradeAdapterMock {
/* ============ Helper Functions ============ */
function withdraw(address _token)
external
{
uint256 balance = ERC20(_token).balanceOf(address(this));
require(ERC20(_token).transfer(msg.sender, balance), "ERC20 transfer failed");
}
/* ============ Trade Functions ============ */
function trade(
address _sourceToken,
address _destinationToken,
address _destinationAddress,
uint256 _sourceQuantity,
uint256 /* _minDestinationQuantity */
)
external
{
uint256 destinationBalance = ERC20(_destinationToken).balanceOf(address(this));
require(ERC20(_sourceToken).transferFrom(_destinationAddress, address(this), _sourceQuantity), "ERC20 TransferFrom failed");
require(ERC20(_destinationToken).transfer(_destinationAddress, destinationBalance), "ERC20 transfer failed");
}
/* ============ Adapter Functions ============ */
function getSpender()
external
view
returns (address)
{
return address(this);
}
function getTradeCalldata(
address _sourceToken,
address _destinationToken,
address _destinationAddress,
uint256 _sourceQuantity,
uint256 _minDestinationQuantity,
bytes memory /* _data */
)
external
view
returns (address, uint256, bytes memory)
{
// Encode method data for SetToken to invoke
bytes memory methodData = abi.encodeWithSignature(
"trade(address,address,address,uint256,uint256)",
_sourceToken,
_destinationToken,
_destinationAddress,
_sourceQuantity,
_minDestinationQuantity
);
return (address(this), 0, methodData);
}
}
WrapAdapterMock.sol 104 lines
/*
Copyright 2021 Index Coop.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract WrapAdapterMock is ERC20 {
constructor(address _owner, uint256 _initAmount) public ERC20("Wrapped Token", "WTOKEN") {
_mint(_owner, _initAmount);
}
/* ========= Wrapped Token Functions ========== */
function mint(IERC20 _underlying, uint256 _amount) external {
_underlying.transferFrom(msg.sender, address(this), _amount);
_mint(msg.sender, _amount);
}
function mintWithEther(uint256 _amount) external payable {
require(msg.value == _amount, "msg.value to low");
_mint(msg.sender, _amount);
}
function burn(IERC20 _underlying, uint256 _amount) external {
_burn(msg.sender, _amount);
_underlying.transfer(msg.sender, _amount);
}
function burnWithEther(uint256 _amount) external {
_burn(msg.sender, _amount);
msg.sender.transfer(_amount);
}
receive() external payable {}
/* ========= Wrap Adapter Functions =========== */
address public constant ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
function getWrapCallData(
address _underlyingToken,
address /* _wrappedToken */,
uint256 _underlyingUnits
)
external
view
returns (address _subject, uint256 _value, bytes memory _calldata)
{
if (_underlyingToken == ETH_TOKEN_ADDRESS) {
bytes memory data = abi.encodeWithSelector(this.mintWithEther.selector, _underlyingUnits);
return (address(this), _underlyingUnits, data);
} else {
bytes memory data = abi.encodeWithSelector(this.mint.selector, _underlyingToken, _underlyingUnits);
return (address(this), 0, data);
}
}
function getUnwrapCallData(
address _underlyingToken,
address /* _wrappedToken */,
uint256 _wrappedTokenUnits
)
external
view
returns
(address _subject, uint256 _value, bytes memory _calldata)
{
if (_underlyingToken == ETH_TOKEN_ADDRESS) {
bytes memory data = abi.encodeWithSelector(this.burnWithEther.selector, _wrappedTokenUnits);
return (address(this), 0, data);
} else {
bytes memory data = abi.encodeWithSelector(this.burn.selector, _underlyingToken, _wrappedTokenUnits);
return (address(this), 0, data);
}
}
function getSpenderAddress(
address /* underlyingToken */,
address /* _wrappedToken */
)
external
view
returns(address)
{
return address(this);
}
}
WrappedfCashFactoryMock.sol 50 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { IWrappedfCashFactory } from "../interfaces/IWrappedfCashFactory.sol";
import { WrappedfCashMock } from "./WrappedfCashMock.sol";
// mock class using BasicToken
contract WrappedfCashFactoryMock is IWrappedfCashFactory {
mapping(uint16 => mapping(uint40 => address)) paramsToAddress;
bool private revertComputeAddress;
function registerWrapper(uint16 _currencyId, uint40 _maturity, address _fCashWrapper) external {
paramsToAddress[_currencyId][_maturity] = _fCashWrapper;
}
function deployWrapper(uint16 _currencyId, uint40 _maturity) external override returns(address) {
return computeAddress(_currencyId, _maturity);
}
function computeAddress(uint16 _currencyId, uint40 _maturity) public view override returns(address) {
require(!revertComputeAddress, "Test revertion ComputeAddress");
return paramsToAddress[_currencyId][_maturity];
}
function setRevertComputeAddress(bool _revertComputeAddress) external{
revertComputeAddress = _revertComputeAddress;
}
}
WrappedfCashMock.sol 199 lines
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenType, IWrappedfCash } from "../interfaces/IWrappedfCash.sol";
// mock class using BasicToken
contract WrappedfCashMock is ERC20, IWrappedfCash {
uint256 private fCashId;
uint40 private maturity;
bool private matured;
uint16 private currencyId;
uint8 private marketIndex;
IERC20 private underlyingToken;
int256 private underlyingPrecision;
IERC20 private assetToken;
int256 private assetPrecision;
TokenType private tokenType;
IERC20 private weth;
bool private revertDecodedID;
uint256 public redeemTokenReturned;
uint256 public mintTokenSpent;
address internal constant ETH_ADDRESS = address(0);
constructor (IERC20 _assetToken, IERC20 _underlyingToken, IERC20 _weth) public ERC20("FCashMock", "FCM") {
assetToken = _assetToken;
underlyingToken = _underlyingToken;
weth = _weth;
}
function initialize(uint16 _currencyId, uint40 _maturity) external override {
currencyId = _currencyId;
maturity = _maturity;
}
/// @notice Mints wrapped fCash ERC20 tokens
function mintViaAsset(
uint256 depositAmountExternal,
uint88 fCashAmount,
address receiver,
uint32 /* minImpliedRate */
) external override{
uint256 assetTokenAmount = mintTokenSpent == 0 ? depositAmountExternal : mintTokenSpent;
require(assetToken.transferFrom(msg.sender, address(this), assetTokenAmount), "WrappedfCashMock: Transfer failed");
_mint(receiver, fCashAmount);
}
function previewMint(
uint256 fCashAmount
)
external
view
override
returns(uint256) {
return mintTokenSpent == 0 ? fCashAmount : mintTokenSpent;
}
function previewRedeem(
uint256 fCashAmount
)
external
view
override
returns(uint256) {
return redeemTokenReturned == 0 ? fCashAmount : redeemTokenReturned;
}
function mintViaUnderlying(
uint256 depositAmountExternal,
uint88 fCashAmount,
address receiver,
uint32 /* minImpliedRate */
) external override{
uint256 underlyingTokenAmount = mintTokenSpent == 0 ? fCashAmount : mintTokenSpent;
bool transferSuccess;
if(address(underlyingToken) == ETH_ADDRESS) {
transferSuccess = weth.transferFrom(msg.sender, address(this), underlyingTokenAmount);
} else {
transferSuccess = underlyingToken.transferFrom(msg.sender, address(this), underlyingTokenAmount);
}
require(transferSuccess, "WrappedfCashMock: Transfer failed");
_mint(receiver, fCashAmount);
}
function redeemToAsset(
uint256 amount,
address receiver,
uint32 /* maxImpliedRate */
) external override {
_burn(msg.sender, amount);
uint256 assetTokenAmount = redeemTokenReturned == 0 ? amount : redeemTokenReturned;
require(assetToken.transfer(receiver, assetTokenAmount), "WrappedfCashMock: Transfer failed");
}
function redeemToUnderlying(
uint256 amount,
address receiver,
uint32 /* maxImpliedRate */
) external override {
_burn(msg.sender, amount);
uint256 underlyingTokenAmount = redeemTokenReturned == 0 ? amount : redeemTokenReturned;
if(address(underlyingToken) == ETH_ADDRESS) {
weth.transfer(receiver, underlyingTokenAmount);
} else {
underlyingToken.transfer(receiver, underlyingTokenAmount);
}
}
/// @notice Returns the underlying fCash ID of the token
function getfCashId() external override view returns (uint256) {
return fCashId;
}
/// @notice True if the fCash has matured, assets mature exactly on the block time
function hasMatured() external override view returns (bool) {
return matured;
}
/// @notice Returns the components of the fCash idd
function getDecodedID() external override view returns (uint16, uint40) {
require(!revertDecodedID, "Test revertion DecodedID");
return (currencyId, maturity);
}
/// @notice Returns the current market index for this fCash asset. If this returns
/// zero that means it is idiosyncratic and cannot be traded.
function getMarketIndex() external override view returns (uint8) {
return marketIndex;
}
/// @notice Returns the token and precision of the token that this token settles
/// to. For example, fUSDC will return the USDC token address and 1e6. The zero
/// address will represent ETH.
function getUnderlyingToken() public override view returns (IERC20, int256) {
return (underlyingToken, underlyingPrecision);
}
/// @notice Returns the asset token which the fCash settles to. This will be an interest
/// bearing token like a cToken or aToken.
function getAssetToken() public override view returns (IERC20, int256, TokenType) {
return (assetToken, assetPrecision, tokenType);
}
function setMatured(bool _matured) external{
matured = _matured;
}
function setRedeemTokenReturned(uint256 _redeemTokenReturned) external{
redeemTokenReturned = _redeemTokenReturned;
}
function setMintTokenSpent(uint256 _mintTokenSpent) external{
mintTokenSpent = _mintTokenSpent;
}
function setRevertDecodedID(bool _revertDecodedID) external{
revertDecodedID = _revertDecodedID;
}
function getToken(bool useUnderlying) public view override returns (IERC20 token, bool isETH) {
if (useUnderlying) {
(token, /* */) = getUnderlyingToken();
} else {
(token, /* */, /* */) = getAssetToken();
}
isETH = address(token) == ETH_ADDRESS;
}
function kill() external {
selfdestruct(msg.sender);
}
}
WrapV2AdapterMock.sol 121 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title WrapV2AdapterMock
* @author Set Protocol
*
* ERC20 contract that doubles as a wrap token. The wrapToken accepts any underlying token and
* mints/burns the WrapAdapter Token.
*/
contract WrapV2AdapterMock is ERC20 {
address public constant ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ============ Constructor ============ */
constructor() public ERC20("WrapV2Adapter", "WRAPV2") {}
/* ============ External Functions ============ */
/**
* Mints tokens to the sender of the underlying quantity
*/
function deposit(address _underlyingToken, uint256 _underlyingQuantity) payable external {
// Do a transferFrom of the underlyingToken
if (_underlyingToken != ETH_TOKEN_ADDRESS) {
IERC20(_underlyingToken).transferFrom(msg.sender, address(this), _underlyingQuantity);
}
_mint(msg.sender, _underlyingQuantity);
}
/**
* Burns tokens from the sender of the wrapped asset and returns the underlying
*/
function withdraw(address _underlyingToken, uint256 _underlyingQuantity) external {
// Transfer the underlying to the sender
if (_underlyingToken == ETH_TOKEN_ADDRESS) {
msg.sender.transfer(_underlyingQuantity);
} else {
IERC20(_underlyingToken).transfer(msg.sender, _underlyingQuantity);
}
_burn(msg.sender, _underlyingQuantity);
}
/**
* Generates the calldata to wrap an underlying asset into a wrappedToken.
*
* @param _underlyingToken Address of the component to be wrapped
* @param _underlyingUnits Total quantity of underlying units to wrap
*
* @return _subject Target contract address
* @return _value Total quantity of underlying units (if underlying is ETH)
* @return _calldata Wrap calldata
*/
function getWrapCallData(
address _underlyingToken,
address /* _wrappedToken */,
uint256 _underlyingUnits,
address /* _to */,
bytes memory /* _wrapData */
) external view returns (address _subject, uint256 _value, bytes memory _calldata) {
uint256 value = _underlyingToken == ETH_TOKEN_ADDRESS ? _underlyingUnits : 0;
bytes memory callData = abi.encodeWithSignature("deposit(address,uint256)", _underlyingToken, _underlyingUnits);
return (address(this), value, callData);
}
/**
* Generates the calldata to unwrap a wrapped asset into its underlying.
*
* @param _underlyingToken Address of the underlying of the component to be unwrapped
* @param _wrappedTokenUnits Total quantity of wrapped token units to unwrap
*
* @return _subject Target contract address
* @return _value Total quantity of wrapped token units to unwrap. This will always be 0 for unwrapping
* @return _calldata Unwrap calldata
*/
function getUnwrapCallData(
address _underlyingToken,
address /* _wrappedToken */,
uint256 _wrappedTokenUnits,
address /* _to */,
bytes memory /* _wrapData */
) external view returns (address _subject, uint256 _value, bytes memory _calldata) {
bytes memory callData = abi.encodeWithSignature("withdraw(address,uint256)", _underlyingToken, _wrappedTokenUnits);
return (address(this), 0, callData);
}
/**
* Returns the address to approve source tokens for wrapping.
*
* @return address Address of the contract to approve tokens to
*/
function getSpenderAddress(
address /* _underlyingToken */,
address /* _wrappedToken */
) external view returns(address) {
return address(this);
}
}
ZeroExExchangeProxyMock.sol 110 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ZeroExExchangeProxyMock {
// Originally I also wanted to test Custom error handling,
// but refrained from doing so, since the necessary upgrade of ethers lead to a lot of typescript issues.
// TODO: Add Custom error handling test when ethers.js is upgraded to a compatible version
enum ErrorType {
None,
RevertMessage,
CustomError
}
// Mappings to control amount of buy / sell token transfered
mapping(address => uint256) public buyAmountMultipliers;
mapping(address => uint256) public sellAmountMultipliers;
mapping(address => ErrorType) public errorMapping;
string public constant testRevertMessage = "test revert message";
// Method mocking the UniswapFeature of the zeroEx setup in tests
// Returns the `minBuyAmount` of target token to the caller, which needs to be deposited into this contract beforehand
// Original Implementation: https://github.com/0xProject/protocol/blob/development/contracts/zero-ex/contracts/src/features/UniswapFeature.sol#L99
function sellToUniswap(
IERC20[] calldata tokens,
uint256 sellAmount,
uint256 minBuyAmount,
bool // isSushi
)
external
payable
returns (uint256 buyAmount)
{
require(tokens.length > 1, "UniswapFeature/InvalidTokensLength");
IERC20 sellToken = tokens[0];
IERC20 buyToken = tokens[tokens.length - 1];
_throwErrorIfNeeded(sellToken);
uint256 multipliedSellAmount = getSellAmount(sellToken, sellAmount);
sellToken.transferFrom(msg.sender, address(this), multipliedSellAmount);
buyAmount = getBuyAmount(buyToken, minBuyAmount);
buyToken.transfer(msg.sender, buyAmount);
}
function _throwErrorIfNeeded(IERC20 sellToken) internal
{
if (errorMapping[address(sellToken)] == ErrorType.RevertMessage) {
revert(testRevertMessage);
}
}
function getBuyAmount(
IERC20 buyToken,
uint256 minBuyAmount
) public view returns (uint256 buyAmount) {
uint256 buyMultiplier = buyAmountMultipliers[address(buyToken)];
if (buyMultiplier == 0) {
buyAmount = minBuyAmount;
}
else{
buyAmount = (minBuyAmount * buyMultiplier) / 10**18;
}
}
// Function to adjust the amount of buy token that will be returned
// Set to 0 to disable / i.e. always return exact minBuyAmount
function setBuyMultiplier(
IERC20 buyToken,
uint256 multiplier
) public {
buyAmountMultipliers[address(buyToken)] = multiplier;
}
function getSellAmount(
IERC20 sellToken,
uint256 inputSellAmount
) public view returns (uint256 sellAmount) {
uint256 sellMultiplier = sellAmountMultipliers[address(sellToken)];
if (sellMultiplier == 0) {
sellAmount = inputSellAmount;
}
else{
sellAmount = (inputSellAmount * sellMultiplier) / 10**18;
}
}
// Function to adjust the amount of sell token that will be returned
// Set to 0 to disable / i.e. always return exact minSellAmount
function setSellMultiplier(
IERC20 sellToken,
uint256 multiplier
) public {
sellAmountMultipliers[address(sellToken)] = multiplier;
}
function setErrorMapping(
address sellToken,
ErrorType errorType
) public {
errorMapping[sellToken] = errorType;
}
}
RewardsDistributionRecipient.sol 13 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity ^0.6.10;
abstract contract RewardsDistributionRecipient {
address public rewardsDistribution;
function notifyRewardAmount(uint256 reward) external virtual;
modifier onlyRewardsDistribution() {
require(msg.sender == rewardsDistribution, "Caller is not RewardsDistribution contract");
_;
}
}
StakingRewards.sol 150 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity ^0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
// Inheritance
import { RewardsDistributionRecipient } from "./RewardsDistributionRecipient.sol";
contract StakingRewards is RewardsDistributionRecipient, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;
/* ========== STATE VARIABLES ========== */
IERC20 public rewardsToken;
IERC20 public stakingToken;
uint256 public periodFinish = 0;
uint256 public rewardRate = 0;
uint256 public rewardsDuration = 60 days;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
uint256 private _totalSupply;
mapping(address => uint256) private _balances;
/* ========== CONSTRUCTOR ========== */
constructor(
address _rewardsDistribution,
address _rewardsToken,
address _stakingToken
) public {
rewardsToken = IERC20(_rewardsToken);
stakingToken = IERC20(_stakingToken);
rewardsDistribution = _rewardsDistribution;
}
/* ========== VIEWS ========== */
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
function lastTimeRewardApplicable() public view returns (uint256) {
return Math.min(block.timestamp, periodFinish);
}
function rewardPerToken() public view returns (uint256) {
if (_totalSupply == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored.add(
lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply)
);
}
function earned(address account) public view returns (uint256) {
return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
}
function getRewardForDuration() external view returns (uint256) {
return rewardRate.mul(rewardsDuration);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
_totalSupply = _totalSupply.add(amount);
_balances[msg.sender] = _balances[msg.sender].add(amount);
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
_totalSupply = _totalSupply.sub(amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
function getReward() public nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
function exit() external {
withdraw(_balances[msg.sender]);
getReward();
}
/* ========== RESTRICTED FUNCTIONS ========== */
function notifyRewardAmount(uint256 reward) external override onlyRewardsDistribution updateReward(address(0)) {
if (block.timestamp >= periodFinish) {
rewardRate = reward.div(rewardsDuration);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardRate = reward.add(leftover).div(rewardsDuration);
}
// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint balance = rewardsToken.balanceOf(address(this));
require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
emit RewardAdded(reward);
}
/* ========== MODIFIERS ========== */
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
/* ========== EVENTS ========== */
event RewardAdded(uint256 reward);
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
}
StakingRewardsV2.sol 193 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity ^0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
// Inheritance
import { RewardsDistributionRecipient } from "./RewardsDistributionRecipient.sol";
// NOTE: V2 allows setting of rewardsDuration in constructor
contract StakingRewardsV2 is RewardsDistributionRecipient, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;
/* ========== STATE VARIABLES ========== */
/// @notice Token to be distributed to stakers
IERC20 public rewardsToken;
/// @notice Token to be staked in the contract
IERC20 public stakingToken;
/// @notice Timestamp at which the staking rewards end
uint256 public periodFinish = 0;
/// @notice Number of tokens rewarded per unit time
uint256 public rewardRate = 0;
/// @notice Duration for which rewards are applied
uint256 public rewardsDuration;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
/// @notice Accumulated number of `rewardsToken` per address remaining to be claimed.
/// Once a staker claims the rewards, it is reset to 0.
mapping(address => uint256) public rewards;
/// @notice Total number of staked tokens
uint256 private _totalSupply;
/// @notice Number of staked tokens per address
mapping(address => uint256) private _balances;
/* ========== CONSTRUCTOR ========== */
constructor(
address _rewardsDistribution,
address _rewardsToken,
address _stakingToken,
uint256 _rewardsDuration
) public {
rewardsToken = IERC20(_rewardsToken);
stakingToken = IERC20(_stakingToken);
rewardsDistribution = _rewardsDistribution;
rewardsDuration = _rewardsDuration;
}
/* ========== VIEWS ========== */
// Number of total staked tokens
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
/**
* @notice Get the number of tokens staked by the `account`
* @param account The address of the account to get the balance of
* @return The number of tokens staked
*/
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
/// @return last time when the reward was applied.
function lastTimeRewardApplicable() public view returns (uint256) {
return Math.min(block.timestamp, periodFinish);
}
function rewardPerToken() public view returns (uint256) {
if (_totalSupply == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored.add(
lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply)
);
}
/**
* @notice Accumulated number of `rewardsToken` remaining to be claimed by `address`.
*/
function earned(address account) public view returns (uint256) {
return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
}
/// @notice Get the number of rewardsToken to be rewarded for `rewardsDuration`
function getRewardForDuration() external view returns (uint256) {
return rewardRate.mul(rewardsDuration);
}
/* ========== MUTATIVE FUNCTIONS ========== */
/// @notice Transfer `amount` of stakingToken from `msg.sender` to this contract for staking
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
_totalSupply = _totalSupply.add(amount);
_balances[msg.sender] = _balances[msg.sender].add(amount);
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
/// @notice Unstake `amount` number of staked tokens by `msg.sender`
function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
_totalSupply = _totalSupply.sub(amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
/// @notice Transfer the staking rewards from contract to `msg.sender`
function getReward() public nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
/// @notice Unstake all the staked tokens by `msg.sender` and claim the rewards
function exit() external {
withdraw(_balances[msg.sender]);
getReward();
}
/* ========== RESTRICTED FUNCTIONS ========== */
/**
* @notice Update the number of `rewardsToken` to be rewarded to stakers.
The updated reward is only applicable from the next block.
If the staking period is over, ie, if the current block occurs at or after
`periodFinish` has passed, a fresh reward period starts with
duration=`rewardsDuration`.
* @param reward number of `rewardsToken` to to be rewarded from now on.
*/
function notifyRewardAmount(uint256 reward) external override onlyRewardsDistribution updateReward(address(0)) {
if (block.timestamp >= periodFinish) {
rewardRate = reward.div(rewardsDuration);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardRate = reward.add(leftover).div(rewardsDuration);
}
// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint balance = rewardsToken.balanceOf(address(this));
require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
emit RewardAdded(reward);
}
/* ========== MODIFIERS ========== */
/// @notice Update rewards for an address along with bookkeeping variables
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
/* ========== EVENTS ========== */
event RewardAdded(uint256 reward);
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
}
FTCVesting.sol 92 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
/// @title A vesting contract for full time contributors
/// @author 0xModene
/// @notice You can use this contract to set up vesting for full time DAO contributors
/// @dev All function calls are currently implemented without side effects
contract FTCVesting {
using SafeMath for uint256;
address public index;
address public recipient;
address public treasury;
uint256 public vestingAmount;
uint256 public vestingBegin;
uint256 public vestingCliff;
uint256 public vestingEnd;
uint256 public lastUpdate;
constructor(
address index_,
address recipient_,
address treasury_,
uint256 vestingAmount_,
uint256 vestingBegin_,
uint256 vestingCliff_,
uint256 vestingEnd_
) public {
require(vestingCliff_ >= vestingBegin_, "FTCVester.constructor: cliff is too early");
require(vestingEnd_ > vestingCliff_, "FTCVester.constructor: end is too early");
index = index_;
recipient = recipient_;
treasury = treasury_;
vestingAmount = vestingAmount_;
vestingBegin = vestingBegin_;
vestingCliff = vestingCliff_;
vestingEnd = vestingEnd_;
lastUpdate = vestingBegin;
}
modifier onlyTreasury {
require(msg.sender == treasury, "FTCVester.onlyTreasury: unauthorized");
_;
}
modifier onlyRecipient {
require(msg.sender == recipient, "FTCVester.onlyRecipient: unauthorized");
_;
}
modifier overCliff {
require(block.timestamp >= vestingCliff, "FTCVester.overCliff: cliff not reached");
_;
}
/// @notice Sets new recipient address
/// @param recipient_ new recipient address
function setRecipient(address recipient_) external onlyRecipient {
recipient = recipient_;
}
/// @notice Sets new treasury address
/// @param treasury_ new treasury address
function setTreasury(address treasury_) external onlyTreasury {
treasury = treasury_;
}
/// @notice Allows recipient to claim all currently vested tokens
function claim() external onlyRecipient overCliff {
uint256 amount;
if (block.timestamp >= vestingEnd) {
amount = IERC20(index).balanceOf(address(this));
} else {
amount = vestingAmount.mul(block.timestamp.sub(lastUpdate)).div(vestingEnd.sub(vestingBegin));
lastUpdate = block.timestamp;
}
IERC20(index).transfer(recipient, amount);
}
/// @notice Allows treasury to claw back funds in event of separation from recipient
function clawback() external onlyTreasury {
IERC20(index).transfer(treasury, IERC20(index).balanceOf(address(this)));
}
}
IndexPowah.sol 185 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { StakingRewardsV2 } from "../staking/StakingRewardsV2.sol";
import { IMasterChef } from "../interfaces/IMasterChef.sol";
import { IPair } from "../interfaces/IPair.sol";
import { Vesting } from "./Vesting.sol";
/**
* @title IndexPowah
* @author Set Protocol
*
* An ERC20 token used for tracking the voting power for the Index Coop. The mutative functions of
* the ERC20 interface have been disabled since the token is only designed to count votes for the
* sake of utilizing Snapshot's erc20-balance-of strategy. This contract is inspired by Sushiswap's
* SUSHIPOWAH contract which serves the same purpose.
*/
contract IndexPowah is IERC20, Ownable {
using SafeMath for uint256;
IERC20 public indexToken;
IMasterChef public masterChef;
uint256 public masterChefId;
IPair public uniPair;
IPair public sushiPair;
StakingRewardsV2[] public farms;
Vesting[] public vesting;
/**
* Sets the appropriate state variables for the contract.
*
* @param _owner owner of this contract
* @param _indexToken Index Coop's governance token contract
* @param _uniPair INDEX-WETH Uniswap pair
* @param _sushiPair INDEX-WETH Sushiswap pair
* @param _masterChef Sushiswap MasterChef (Onsen) contract
* @param _farms array of Index Coop staking farms
* @param _vesting array of vesting contracts from the index sale and full time contributors
*/
constructor(
address _owner,
IERC20 _indexToken,
IPair _uniPair,
IPair _sushiPair,
IMasterChef _masterChef,
uint256 _masterChefId,
StakingRewardsV2[] memory _farms,
Vesting[] memory _vesting
)
public
{
indexToken = _indexToken;
uniPair = _uniPair;
sushiPair = _sushiPair;
masterChef = _masterChef;
masterChefId = _masterChefId;
farms = _farms;
vesting = _vesting;
transferOwnership(_owner);
}
/**
* Computes an address's balance of IndexPowah. Balances can not be transfered in the traditional way,
* but are instead computed by the amount of index that an account directly hold, or indirectly holds
* through the staking contracts, vesting contracts, uniswap, and sushiswap.
*
* @param _account the address of the voter
*/
function balanceOf(address _account) public view override returns (uint256) {
uint256 indexAmount = indexToken.balanceOf(_account);
uint256 unclaimedInFarms = _getFarmVotes(_account);
uint256 vestingVotes = _getVestingVotes(_account);
uint256 dexVotes = _getDexVotes(_account, uniPair) + _getDexVotes(_account, sushiPair) + _getMasterChefVotes(_account);
return indexAmount + unclaimedInFarms + vestingVotes + dexVotes;
}
/**
* ONLY OWNER: Adds new Index farms to be tracked
*
* @param _newFarms list of new farms to be tracked
*/
function addFarms(StakingRewardsV2[] calldata _newFarms) external onlyOwner {
for (uint256 i = 0; i < _newFarms.length; i++) {
farms.push(_newFarms[0]);
}
}
/**
* ONLY OWNER: Adds new Index vesting contracts to be tracked
*
* @param _newVesting list of new vesting contracts to be tracked
*/
function addVesting(Vesting[] calldata _newVesting) external onlyOwner {
for (uint256 i = 0; i < _newVesting.length; i++) {
vesting.push(_newVesting[i]);
}
}
/**
* ONLY OWNER: Updates the MasterChef contract and pool ID
*
* @param _newMasterChef address of the new MasterChef contract
* @param _newMasterChefId new pool id for the index-eth MasterChef rewards
*/
function updateMasterChef(IMasterChef _newMasterChef, uint256 _newMasterChefId) external onlyOwner {
masterChef = _newMasterChef;
masterChefId = _newMasterChefId;
}
function _getFarmVotes(address _account) internal view returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < farms.length; i++) {
sum += farms[i].earned(_account);
}
return sum;
}
function _getVestingVotes(address _account) internal view returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < vesting.length; i++) {
if(vesting[i].recipient() == _account) {
sum += indexToken.balanceOf(address(vesting[i]));
}
}
return sum;
}
function _getDexVotes(address _account, IPair pair) internal view returns (uint256) {
uint256 lpBalance = pair.balanceOf(_account);
return _getDexVotesFromBalance(lpBalance, pair);
}
function _getMasterChefVotes(address _account) internal view returns (uint256) {
(uint256 lpBalance,) = masterChef.userInfo(masterChefId, _account);
return _getDexVotesFromBalance(lpBalance, sushiPair);
}
function _getDexVotesFromBalance(uint256 lpBalance, IPair pair) internal view returns (uint256) {
uint256 lpIndex = indexToken.balanceOf(address(pair));
uint256 lpTotal = pair.totalSupply();
if (lpTotal == 0) return 0;
return lpIndex.mul(lpBalance).div(lpTotal);
}
/**
* These functions are not used, but have been left in to keep the token ERC20 compliant
*/
function name() public pure returns (string memory) { return "INDEXPOWAH"; }
function symbol() public pure returns (string memory) { return "INDEXPOWAH"; }
function decimals() public pure returns(uint8) { return 18; }
function totalSupply() public view override returns (uint256) { return indexToken.totalSupply(); }
function allowance(address, address) public view override returns (uint256) { return 0; }
function transfer(address, uint256) public override returns (bool) { return false; }
function approve(address, uint256) public override returns (bool) { return false; }
function transferFrom(address, address, uint256) public override returns (bool) { return false; }
}
MerkleDistributor.sol 47 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { MerkleProof } from "@openzeppelin/contracts/cryptography/MerkleProof.sol";
import { IMerkleDistributor } from "../interfaces/IMerkleDistributor.sol";
contract MerkleDistributor is IMerkleDistributor {
address public immutable override token;
bytes32 public immutable override merkleRoot;
// This is a packed array of booleans.
mapping(uint256 => uint256) private claimedBitMap;
constructor(address token_, bytes32 merkleRoot_) public {
token = token_;
merkleRoot = merkleRoot_;
}
function isClaimed(uint256 index) public view override returns (bool) {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedWord = claimedBitMap[claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask == mask;
}
function _setClaimed(uint256 index) private {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
}
function claim(uint256 index, address account, uint256 amount, bytes32[] calldata merkleProof) external override {
require(!isClaimed(index), "MerkleDistributor: Drop already claimed.");
// Verify the merkle proof.
bytes32 node = keccak256(abi.encodePacked(index, account, amount));
require(MerkleProof.verify(merkleProof, merkleRoot, node), "MerkleDistributor: Invalid proof.");
// Mark it claimed and send the token.
_setClaimed(index);
require(IERC20(token).transfer(account, amount), "MerkleDistributor: Transfer failed.");
emit Claimed(index, account, amount);
}
}
OtcEscrow.sol 146 lines
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { Vesting } from "./Vesting.sol";
/**
* @title OtcEscrow
* @author Badger DAO (Modified by Set Protocol)
*
* A simple OTC swap contract allowing two users to set the parameters of an OTC
* deal in the constructor arguments, and deposits the sold tokens into a vesting
* contract when a swap is completed.
*/
contract OtcEscrow {
using SafeMath for uint256;
using SafeERC20 for IERC20;
/* ========== Events =========== */
event VestingDeployed(address vesting);
/* ====== Modifiers ======== */
/**
* Throws if the sender is not Index Gov
*/
modifier onlyIndexGov() {
require(msg.sender == indexGov, "unauthorized");
_;
}
/**
* Throws if run more than once
*/
modifier onlyOnce() {
require(!hasRun, "swap already executed");
hasRun = true;
_;
}
/* ======== State Variables ======= */
address public usdc;
address public index;
address public indexGov;
address public beneficiary;
uint256 public vestingStart;
uint256 public vestingEnd;
uint256 public vestingCliff;
uint256 public usdcAmount;
uint256 public indexAmount;
bool hasRun;
/* ====== Constructor ======== */
/**
* Sets the state variables that encode the terms of the OTC sale
*
* @param _beneficiary Address that will purchase INDEX
* @param _indexGov Address that will receive USDC
* @param _vestingStart Timestamp of vesting start
* @param _vestingCliff Timestamp of vesting cliff
* @param _vestingEnd Timestamp of vesting end
* @param _usdcAmount Amount of USDC swapped for the sale
* @param _indexAmount Amount of INDEX swapped for the sale
* @param _usdcAddress Address of the USDC token
* @param _indexAddress Address of the Index token
*/
constructor(
address _beneficiary,
address _indexGov,
uint256 _vestingStart,
uint256 _vestingCliff,
uint256 _vestingEnd,
uint256 _usdcAmount,
uint256 _indexAmount,
address _usdcAddress,
address _indexAddress
) public {
beneficiary = _beneficiary;
indexGov = _indexGov;
vestingStart = _vestingStart;
vestingCliff = _vestingCliff;
vestingEnd = _vestingEnd;
usdcAmount = _usdcAmount;
indexAmount = _indexAmount;
usdc = _usdcAddress;
index = _indexAddress;
hasRun = false;
}
/* ======= External Functions ======= */
/**
* Executes the OTC deal. Sends the USDC from the beneficiary to Index Governance, and
* locks the INDEX in the vesting contract. Can only be called once.
*/
function swap() external onlyOnce {
require(IERC20(index).balanceOf(address(this)) >= indexAmount, "insufficient INDEX");
// Transfer expected USDC from beneficiary
IERC20(usdc).safeTransferFrom(beneficiary, address(this), usdcAmount);
// Create Vesting contract
Vesting vesting = new Vesting(index, beneficiary, indexAmount, vestingStart, vestingCliff, vestingEnd);
// Transfer index to vesting contract
IERC20(index).safeTransfer(address(vesting), indexAmount);
// Transfer USDC to index governance
IERC20(usdc).safeTransfer(indexGov, usdcAmount);
emit VestingDeployed(address(vesting));
}
/**
* Return INDEX to Index Governance to revoke the deal
*/
function revoke() external onlyIndexGov {
uint256 indexBalance = IERC20(index).balanceOf(address(this));
IERC20(index).safeTransfer(indexGov, indexBalance);
}
/**
* Recovers USDC accidentally sent to the contract
*/
function recoverUsdc() external {
uint256 usdcBalance = IERC20(usdc).balanceOf(address(this));
IERC20(usdc).safeTransfer(beneficiary, usdcBalance);
}
}
Prt.sol 53 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* @title Prt
* @author Index Cooperative
* @notice Standard ERC20 token with a fixed supply allocated to a distributor. Associated with a SetToken.
*/
contract Prt is ERC20 {
/// @notice Address of the SetToken associated with this Prt token.
address public immutable setToken;
/**
* @notice Constructor for the Prt token.
* @dev Mints the total supply of tokens and assigns them to the distributor.
* @param _name The name of the Prt token.
* @param _symbol The symbol of the Prt token.
* @param _setToken The address of the SetToken associated with this Prt token.
* @param _distributor The address that will receive and distribute the total supply of Prt tokens.
* @param _totalSupply The total supply of Prt tokens to be minted and distributed.
*/
constructor(
string memory _name,
string memory _symbol,
address _setToken,
address _distributor,
uint256 _totalSupply
) public
ERC20(_name, _symbol)
{
setToken = _setToken;
_mint(_distributor, _totalSupply);
}
}
Vesting.sol 60 lines
// SPDX-License-Identifier: Apache License, Version 2.0
pragma solidity ^0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
contract Vesting {
using SafeMath for uint256;
address public index;
address public recipient;
uint256 public vestingAmount;
uint256 public vestingBegin;
uint256 public vestingCliff;
uint256 public vestingEnd;
uint256 public lastUpdate;
constructor(
address index_,
address recipient_,
uint256 vestingAmount_,
uint256 vestingBegin_,
uint256 vestingCliff_,
uint256 vestingEnd_
) public {
require(vestingBegin_ >= block.timestamp, "TreasuryVester.constructor: vesting begin too early");
require(vestingCliff_ >= vestingBegin_, "TreasuryVester.constructor: cliff is too early");
require(vestingEnd_ > vestingCliff_, "TreasuryVester.constructor: end is too early");
index = index_;
recipient = recipient_;
vestingAmount = vestingAmount_;
vestingBegin = vestingBegin_;
vestingCliff = vestingCliff_;
vestingEnd = vestingEnd_;
lastUpdate = vestingBegin;
}
function setRecipient(address recipient_) public {
require(msg.sender == recipient, "TreasuryVester.setRecipient: unauthorized");
recipient = recipient_;
}
function claim() public {
require(block.timestamp >= vestingCliff, "TreasuryVester.claim: not time yet");
uint256 amount;
if (block.timestamp >= vestingEnd) {
amount = IERC20(index).balanceOf(address(this));
} else {
amount = vestingAmount.mul(block.timestamp.sub(lastUpdate)).div(vestingEnd.sub(vestingBegin));
lastUpdate = block.timestamp;
}
IERC20(index).transfer(recipient, amount);
}
}
WithdrawTokens.sol 34 lines
/*
Copyright 2024 Index Cooperative
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title WithdrawTokens
* Contract to deploy to addresses where tokens (or eth) were accidentally sent
*/
contract WithdrawTokens is Ownable {
function withdraw(IERC20 token, uint256 amount) external onlyOwner {
if (address(token) == address(0)) {
payable(msg.sender).transfer(amount);
} else {
token.transfer(msg.sender, amount);
}
}
}
FLIRebalanceViewer.sol 259 lines
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { FlexibleLeverageStrategyExtension } from "../adapters/FlexibleLeverageStrategyExtension.sol";
import { IFLIStrategyExtension } from "../interfaces/IFLIStrategyExtension.sol";
import { IQuoter } from "../interfaces/IQuoter.sol";
import { IUniswapV2Router } from "../interfaces/IUniswapV2Router.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
import { StringArrayUtils } from "../lib/StringArrayUtils.sol";
/**
* @title FLIRebalanceViewer
* @author Set Protocol
*
* Viewer contract for FlexibleLeverageStrategyExtension. Used by keeper bots to determine which exchanges to use when rebalancing.
* This contract can only determine whether to use Uniswap V3 or Uniswap V2 (or forks) for rebalancing. Since AMMTradeSplitter adheres to
* the Uniswap V2 router interface, this contract is compatible with that as well.
*/
contract FLIRebalanceViewer {
using PreciseUnitMath for uint256;
using SafeMath for uint256;
using StringArrayUtils for string[];
/* ============ Structs ============ */
struct ActionInfo {
string[] exchangeNames; // List of enabled exchange names
FlexibleLeverageStrategyExtension.ShouldRebalance[] rebalanceActions; // List of rebalance actions with respect to exchangeNames
uint256 uniV3Index; // Index of Uni V3 in both lists
uint256 uniV2Index; // Index of Uni V2 in both lists
uint256 minLeverage; // Minimum leverage ratio of strategy
uint256 maxLeverage; // Maximum leverage ratio of strategy
uint256[] chunkSendQuantity; // Size of rebalances (quoted in sell asset units)
address sellAsset; // Address of asset to sell during rebalance
address buyAsset; // Address of asset to buy during rebalance
bool isLever; // Whether the rebalance is a lever or delever
}
/* ============ State Variables ============ */
IFLIStrategyExtension public fliStrategyExtension;
IQuoter public uniswapV3Quoter;
IUniswapV2Router public uniswapV2Router;
string public uniswapV3ExchangeName;
string public uniswapV2ExchangeName;
/* ============ Constructor ============ */
/**
* Sets state variables
*
* @param _fliStrategyExtension FlexibleLeverageStrategyAdapter contract address
* @param _uniswapV3Quoter Uniswap V3 Quoter contract address
* @param _uniswapV2Router Uniswap v2 Router contract address
* @param _uniswapV3ExchangeName Name of Uniswap V3 exchange in Set's IntegrationRegistry (ex: UniswapV3ExchangeAdapter)
* @param _uniswapV2ExchangeName Name of Uniswap V2 exchange in Set's IntegrationRegistry (ex: AMMSplitterExchangeAdapter)
*/
constructor(
IFLIStrategyExtension _fliStrategyExtension,
IQuoter _uniswapV3Quoter,
IUniswapV2Router _uniswapV2Router,
string memory _uniswapV3ExchangeName,
string memory _uniswapV2ExchangeName
)
public
{
fliStrategyExtension = _fliStrategyExtension;
uniswapV3Quoter = _uniswapV3Quoter;
uniswapV2Router = _uniswapV2Router;
uniswapV3ExchangeName = _uniswapV3ExchangeName;
uniswapV2ExchangeName = _uniswapV2ExchangeName;
}
/* =========== External Functions ============ */
/**
* Gets the priority order for which exchange should be used while rebalancing. Mimics the interface for
* shouldRebalanceWithBound of FlexibleLeverageStrategyExtension. Note: this function is not marked as view
* due to a quirk in the Uniswap V3 Quoter contract, but should be static called to save gas
*
* @param _minLeverageRatio Min leverage ratio
* @param _maxLeverageRatio Max leverage ratio
*
* @return string[] memory Ordered array of exchange names to use. Earlier elements in the array produce the best trades
* @return ShouldRebalance[] memory Array of ShouldRebalance Enums. Ordered relative to returned exchange names array
*/
function shouldRebalanceWithBounds(
uint256 _minLeverageRatio,
uint256 _maxLeverageRatio
)
external
returns(string[2] memory, FlexibleLeverageStrategyExtension.ShouldRebalance[2] memory)
{
ActionInfo memory actionInfo = _getActionInfo(_minLeverageRatio, _maxLeverageRatio);
(uint256 uniswapV3Price, uint256 uniswapV2Price) = _getPrices(actionInfo);
return _getExchangePriority(
uniswapV3Price,
uniswapV2Price,
actionInfo
);
}
/* ================= Internal Functions ================= */
/**
* Fetches prices for rebalancing trades on Uniswap V3 and Uniswap V2. Trade sizes are determined by FlexibleLeverageStrategyExtension's
* getChunkRebalanceNotional.
*
* @param _actionInfo ActionInfo struct
*
* @return uniswapV3Price price of rebalancing trade on Uniswap V3 (scaled by trade size)
* @return uniswapV2Price price of rebalancing trade on Uniswap V2 (scaled by trade size)
*/
function _getPrices(ActionInfo memory _actionInfo) internal returns (uint256 uniswapV3Price, uint256 uniswapV2Price) {
uniswapV3Price = _getV3Price(_actionInfo.chunkSendQuantity[_actionInfo.uniV3Index], _actionInfo.isLever);
uniswapV2Price = _getV2Price(
_actionInfo.chunkSendQuantity[_actionInfo.uniV2Index],
_actionInfo.isLever, _actionInfo.sellAsset, _actionInfo.buyAsset
);
}
/**
* Fetches price of a Uniswap V3 trade. Uniswap V3 fetches quotes using a write function that always reverts. This means that
* this function cannot be view only. Additionally, the Uniswap V3 quoting function cannot be static called in solidity due to the
* internal revert. To save on gas, static call the top level shouldRebalanceWithBounds function when interacting with this contact
*
* @param _sellSize quantity of asset to sell
* @param _isLever whether FLI needs to lever or delever
*
* @return uint256 price of trade on Uniswap V3
*/
function _getV3Price(uint256 _sellSize, bool _isLever) internal returns (uint256) {
bytes memory uniswapV3TradePath = _isLever ?
fliStrategyExtension.getExchangeSettings(uniswapV3ExchangeName).leverExchangeData :
fliStrategyExtension.getExchangeSettings(uniswapV3ExchangeName).deleverExchangeData;
uint256 outputAmount = uniswapV3Quoter.quoteExactInput(uniswapV3TradePath, _sellSize);
// Divide to get ratio of quote / base asset. Don't care about decimals here. Standardizes to 10e18 with preciseDiv
return outputAmount.preciseDiv(_sellSize);
}
/**
* Fetches price of a Uniswap V2 trade
*
* @param _sellSize quantity of asset to sell
* @param _isLever whether FLI needs to lever or delever
*
* @return uint256 price of trade on Uniswap V2
*/
function _getV2Price(uint256 _sellSize, bool _isLever, address _sellAsset, address _buyAsset) internal view returns (uint256) {
bytes memory uniswapV2TradePathRaw = _isLever ?
fliStrategyExtension.getExchangeSettings(uniswapV2ExchangeName).leverExchangeData :
fliStrategyExtension.getExchangeSettings(uniswapV2ExchangeName).deleverExchangeData;
address[] memory uniswapV2TradePath;
if (uniswapV2TradePathRaw.length == 0) {
uniswapV2TradePath = new address[](2);
uniswapV2TradePath[0] = _sellAsset;
uniswapV2TradePath[1] = _buyAsset;
} else {
uniswapV2TradePath = abi.decode(uniswapV2TradePathRaw, (address[]));
}
uint256 outputAmount = uniswapV2Router.getAmountsOut(_sellSize, uniswapV2TradePath)[uniswapV2TradePath.length.sub(1)];
// Divide to get ratio of quote / base asset. Don't care about decimals here. Standardizes to 10e18 with preciseDiv
return outputAmount.preciseDiv(_sellSize);
}
/**
* Gets the ordered priority of which exchanges to use for a rebalance
*
* @param _uniswapV3Price price of rebalance trade on Uniswap V3
* @param _uniswapV2Price price of rebalance trade on Uniswap V2
* @param _actionInfo ActionInfo struct
*
* @return string[] memory Ordered array of exchange names to use. Earlier elements in the array produce the best trades
* @return ShouldRebalance[] memory Array of ShouldRebalance Enums. Ordered relative to returned exchange names array
*/
function _getExchangePriority(
uint256 _uniswapV3Price,
uint256 _uniswapV2Price,
ActionInfo memory _actionInfo
)
internal
view
returns (string[2] memory, FlexibleLeverageStrategyExtension.ShouldRebalance[2] memory)
{
// If no rebalance is required, set price to 0 so it is ordered last
if (_actionInfo.rebalanceActions[_actionInfo.uniV3Index] == FlexibleLeverageStrategyExtension.ShouldRebalance.NONE) _uniswapV3Price = 0;
if (_actionInfo.rebalanceActions[_actionInfo.uniV2Index] == FlexibleLeverageStrategyExtension.ShouldRebalance.NONE) _uniswapV2Price = 0;
if (_uniswapV3Price > _uniswapV2Price) {
return ([ uniswapV3ExchangeName, uniswapV2ExchangeName ],
[ _actionInfo.rebalanceActions[_actionInfo.uniV3Index], _actionInfo.rebalanceActions[_actionInfo.uniV2Index] ]);
} else {
return ([ uniswapV2ExchangeName, uniswapV3ExchangeName ],
[ _actionInfo.rebalanceActions[_actionInfo.uniV2Index], _actionInfo.rebalanceActions[_actionInfo.uniV3Index] ]);
}
}
/**
* Creates the an ActionInfo struct containing information about the rebalancing action
*
* @param _minLeverage Min leverage ratio
* @param _maxLeverage Max leverage ratio
*
* @return actionInfo Populated ActionInfo struct
*/
function _getActionInfo(uint256 _minLeverage, uint256 _maxLeverage) internal view returns (ActionInfo memory actionInfo) {
(actionInfo.exchangeNames, actionInfo.rebalanceActions) = fliStrategyExtension.shouldRebalanceWithBounds(
_minLeverage,
_maxLeverage
);
(actionInfo.uniV3Index, ) = actionInfo.exchangeNames.indexOf(uniswapV3ExchangeName);
(actionInfo.uniV2Index, ) = actionInfo.exchangeNames.indexOf(uniswapV2ExchangeName);
actionInfo.minLeverage = _minLeverage;
actionInfo.maxLeverage = _maxLeverage;
(actionInfo.chunkSendQuantity, actionInfo.sellAsset, actionInfo.buyAsset) = fliStrategyExtension.getChunkRebalanceNotional(
actionInfo.exchangeNames
);
actionInfo.isLever = actionInfo.sellAsset == fliStrategyExtension.getStrategy().borrowAsset;
}
}
SetTokenRateViewer.sol 59 lines
/*
Copyright 2023 IndexCoop.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IRateProvider } from "../interfaces/external/IRateProvider.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
/**
* @title SetTokenRateReviewer
* @author FlattestWhite
*
* A RateProvider contract that provides the amount of component per set token through the getRate function.
* Used by balancer in their liquidity pools.
* https://github.com/balancer-labs/metastable-rate-providers/blob/master/contracts/WstETHRateProvider.sol
*/
contract SetTokenRateViewer is IRateProvider {
using Address for address;
using SafeMath for uint256;
using SafeCast for int256;
ISetToken public immutable setToken;
IERC20 public immutable component;
constructor(ISetToken _setToken, IERC20 _component) public {
setToken = _setToken;
component = _component;
}
/* =========== External Functions ============ */
/**
* @return the amount of component per set token.
*/
function getRate() external view override returns (uint256) {
return setToken.getTotalComponentRealUnits(address(component)).toUint256();
}
}
FlashLoanReceiverBaseV2.sol 31 lines
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import { SafeMath } from '@openzeppelin/contracts/math/SafeMath.sol';
import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import { IFlashLoanReceiverV2 } from './../../../contracts/interfaces/IFlashLoanReceiverV2.sol';
import { ILendingPoolAddressesProviderV2 } from './../../../contracts/interfaces/ILendingPoolAddressesProviderV2.sol';
import { ILendingPoolV2 } from './../../../contracts/interfaces/ILendingPoolV2.sol';
import "./utils/Withdrawable.sol";
/**
!!!
Never keep funds permanently on your FlashLoanReceiverBase contract as they could be
exposed to a 'griefing' attack, where the stored funds are used by an attacker.
!!!
*/
abstract contract FlashLoanReceiverBaseV2 is IFlashLoanReceiverV2 {
using SafeERC20 for IERC20;
using SafeMath for uint256;
ILendingPoolAddressesProviderV2 public immutable override ADDRESSES_PROVIDER;
ILendingPoolV2 public immutable override LENDING_POOL;
constructor(address provider) public {
ADDRESSES_PROVIDER = ILendingPoolAddressesProviderV2(provider);
LENDING_POOL = ILendingPoolV2(ILendingPoolAddressesProviderV2(provider).getLendingPool());
}
receive() payable external {}
}
DataTypes.sol 52 lines
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.10;
/**
* @dev This is the Aave V2 DataTypes library.
*/
library DataTypes {
// refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties.
struct ReserveData {
//stores the reserve configuration
ReserveConfigurationMap configuration;
//the liquidity index. Expressed in ray
uint128 liquidityIndex;
//variable borrow index. Expressed in ray
uint128 variableBorrowIndex;
//the current supply rate. Expressed in ray
uint128 currentLiquidityRate;
//the current variable borrow rate. Expressed in ray
uint128 currentVariableBorrowRate;
//the current stable borrow rate. Expressed in ray
uint128 currentStableBorrowRate;
uint40 lastUpdateTimestamp;
//tokens addresses
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
//address of the interest rate strategy
address interestRateStrategyAddress;
//the id of the reserve. Represents the position in the list of the active reserves
uint8 id;
}
struct ReserveConfigurationMap {
//bit 0-15: LTV
//bit 16-31: Liq. threshold
//bit 32-47: Liq. bonus
//bit 48-55: Decimals
//bit 56: Reserve is active
//bit 57: reserve is frozen
//bit 58: borrowing is enabled
//bit 59: stable rate borrowing enabled
//bit 60-63: reserved
//bit 64-79: reserve factor
uint256 data;
}
struct UserConfigurationMap {
uint256 data;
}
enum InterestRateMode {NONE, STABLE, VARIABLE}
}
Withdrawable.sol 38 lines
pragma solidity ^0.6.10;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
Ensures that any contract that inherits from this contract is able to
withdraw funds that are accidentally received or stuck.
*/
contract Withdrawable is Ownable {
using SafeERC20 for ERC20;
address constant ETHER = address(0);
event LogWithdraw(
address indexed _from,
address indexed _assetAddress,
uint amount
);
/**
* @dev Withdraw asset.
* @param _assetAddress Asset to be withdrawn.
*/
function withdraw(address _assetAddress) public onlyOwner {
uint assetBalance;
if (_assetAddress == ETHER) {
address self = address(this); // workaround for a possible solidity bug
assetBalance = self.balance;
msg.sender.transfer(assetBalance);
} else {
assetBalance = ERC20(_assetAddress).balanceOf(address(this));
ERC20(_assetAddress).safeTransfer(msg.sender, assetBalance);
}
emit LogWithdraw(msg.sender, _assetAddress, assetBalance);
}
}
UniSushiV2Library.sol 73 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
library UniSushiV2Library {
using SafeMath for uint;
// returns sorted token addresses, used to handle return values from pairs sorted in this order
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}
// fetches and sorts the reserves for a pair
function getReserves(address pair, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pair).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}
// performs chained getAmountOut calculations on any number of pairs
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
// performs chained getAmountIn calculations on any number of pairs
function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
}
}
}
Read Contract
ADDRESSES_PROVIDER 0x0542975c → address
POOL 0x7535d246 → address
ROUNDING_ERROR_MARGIN 0x848af32d → uint256
aaveLeverageModule 0x1d2d88ca → address
addresses 0xda0321cd → address, address, address, address, address, address, address
debtIssuanceModule 0xaf6f220a → address
getLeveragedTokenData 0x7452ed1e → tuple
setController 0x8b2704ec → address
Write Contract 10 functions
These functions modify contract state and require a wallet transaction to execute.
approveSetToken 0xb7711c63
address _setToken
approveToken 0x80b2edd8
address _token
approveTokens 0xac43070b
address[] _tokens
executeOperation 0x1b11d0ff
address asset
uint256 amount
uint256 premium
address initiator
bytes params
returns: bool
getIssueExactSet 0xd73fbf79
address _setToken
uint256 _setAmount
tuple _swapDataDebtForCollateral
tuple _swapDataInputToken
returns: uint256
getRedeemExactSet 0xf3df9bb3
address _setToken
uint256 _setAmount
tuple _swapDataCollateralForDebt
tuple _swapDataOutputToken
returns: uint256
issueExactSetFromERC20 0x1f3d9185
address _setToken
uint256 _setAmount
address _inputToken
uint256 _maxAmountInputToken
tuple _swapDataDebtForCollateral
tuple _swapDataInputToken
issueExactSetFromETH 0x66d8229b
address _setToken
uint256 _setAmount
tuple _swapDataDebtForCollateral
tuple _swapDataInputToken
redeemExactSetForERC20 0xf4694e59
address _setToken
uint256 _setAmount
address _outputToken
uint256 _minAmountOutputToken
tuple _swapDataCollateralForDebt
tuple _swapDataOutputToken
redeemExactSetForETH 0xcf40df38
address _setToken
uint256 _setAmount
uint256 _minAmountOutputToken
tuple _swapDataCollateralForDebt
tuple _swapDataOutputToken
Token Balances (4)
View Transfers →Recent Transactions
No transactions found for this address