Address Contract Verified
Address
0x931cf9F135a0cd54922256aAc33ca6151Ed735d3
Balance
0 ETH
Nonce
1
Code Size
13609 bytes
Creator
0x6d228Fa4...E89A at tx 0xa100bb9a...d42136
Indexed Transactions
0
Contract Bytecode
13609 bytes
0x6080604052600436106101385760003560e01c80638cf16261116100ab578063b34cbaf71161006f578063b34cbaf714610413578063be52003914610447578063dde643031461047d578063e7b43da51461049d578063f2fde38b146104bd578063fa40a004146104dd57600080fd5b80638cf16261146103535780638da5cb5b1461037357806394fa3add14610391578063a143e5f7146103b1578063a7e8489d146103df57600080fd5b80633e4504c8116100fd5780633e4504c81461024c5780636f0ad2ca1461026c5780636fca4f8f146102a0578063715018a6146102d457806378892cea146102e95780638cad7fbe1461031d57600080fd5b80624796c61461014457806303a69c9a1461016657806318a4619a1461019c578063338799b0146101e857806339060ea41461021857600080fd5b3661013f57005b600080fd5b34801561015057600080fd5b5061016461015f3660046128e3565b6104fd565b005b34801561017257600080fd5b50610186610181366004612968565b61058f565b60405161019391906129d3565b60405180910390f35b3480156101a857600080fd5b506101d07f0000000000000000000000000e466fc22386997dac23d1f89a43ecb2cb1e76e981565b6040516001600160a01b039091168152602001610193565b3480156101f457600080fd5b50610208610203366004612a2e565b610702565b6040519015158152602001610193565b34801561022457600080fd5b506101d07f000000000000000000000000e37b8c83138caf12e57632d19c06eb561d47e42381565b34801561025857600080fd5b50610164610267366004612a61565b610818565b34801561027857600080fd5b506101d07f0000000000000000000000007c2ca9d502f2409beceafa68e97a176ff805029f81565b3480156102ac57600080fd5b506101d07f0000000000000000000000001111111254eeb25477b68fb85ed929f73a96058281565b3480156102e057600080fd5b5061016461093d565b3480156102f557600080fd5b506101d07f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b34801561032957600080fd5b506101d0610338366004612a2e565b6001602052600090815260409020546001600160a01b031681565b34801561035f57600080fd5b5061016461036e366004612bd9565b610973565b34801561037f57600080fd5b506000546001600160a01b03166101d0565b34801561039d57600080fd5b506101d06103ac366004612a2e565b610b1c565b3480156103bd57600080fd5b506103d16103cc366004612c3a565b610df8565b604051908152602001610193565b3480156103eb57600080fd5b506101d07f000000000000000000000000d998c35b7900b344bbbe6555cc11576942cf309d81565b34801561041f57600080fd5b506101d07f000000000000000000000000e8e8041cb5e3158a0829a19e014ca1cf9109855481565b34801561045357600080fd5b506101d0610462366004612a2e565b6002602052600090815260409020546001600160a01b031681565b34801561048957600080fd5b50610186610498366004612968565b610e53565b3480156104a957600080fd5b506101646104b8366004612c5c565b610fb4565b3480156104c957600080fd5b506101646104d8366004612a2e565b6111b3565b3480156104e957600080fd5b506101646104f83660046128e3565b61124e565b6000546001600160a01b031633146105305760405162461bcd60e51b815260040161052790612d5e565b60405180910390fd5b61058b8282808060200260200160405190810160405280939291908181526020016000905b828210156105815761057260408302860136819003810190612d93565b81526020019060010190610555565b505050505061135b565b5050565b60606000846001600160401b038111156105ab576105ab612ada565b6040519080825280602002602001820160405280156105d4578160200160208202803683370190505b50905060005b858110156106f8577f0000000000000000000000000e466fc22386997dac23d1f89a43ecb2cb1e76e96001600160a01b0316638705c35986868481811061062357610623612dd4565b90506020020160208101906106389190612a2e565b89898581811061064a5761064a612dd4565b905060200201602081019061065f9190612a2e565b6040516001600160e01b031960e085901b1681526001600160a01b03928316600482015291166024820152604401602060405180830381865afa1580156106aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106ce9190612dea565b8282815181106106e0576106e0612dd4565b911515602092830291909101909101526001016105da565b5095945050505050565b60007f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316826001600160a01b03160361074557506001919050565b6001600160a01b03828116600090815260026020526040902054161561076d57506001919050565b6040516394fa3add60e01b81526001600160a01b038316600482015230906394fa3add90602401602060405180830381865afa9250505080156107cd575060408051601f3d908101601f191682019092526107ca91810190612e0c565b60015b610807573d8080156107fb576040519150601f19603f3d011682016040523d82523d6000602084013e610800565b606091505b5050610810565b50600192915050565b506000919050565b600281111561083a5760405163f554afbd60e01b815260040160405180910390fd5b60005a6040805160018082528183019092529192506000919060208083019080368337019050509050868160008151811061087757610877612dd4565b60200260200101906001600160a01b031690816001600160a01b031681525050856001600160a01b03166393a94ca382848888886040516020016108be9493929190612e68565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016108ea929190612fde565b6000604051808303816000875af1158015610909573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610931919081019061313c565b50505050505050505050565b6000546001600160a01b031633146109675760405162461bcd60e51b815260040161052790612d5e565b610971600061146d565b565b60405163095ea7b360e01b81526001600160a01b038381166004830152600019602483015284169063095ea7b3906044016020604051808303816000875af11580156109c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109e79190612dea565b506000807f0000000000000000000000001111111254eeb25477b68fb85ed929f73a9605826001600160a01b031683604051610a23919061321a565b6000604051808303816000865af19150503d8060008114610a60576040519150601f19603f3d011682016040523d82523d6000602084013e610a65565b606091505b509150915081610aa15760408051808201909152601081526f14d5d05417d0d0531317d1905253115160821b6020820152610aa19082906114bd565b60405163095ea7b360e01b81526001600160a01b0385811660048301526000602483015286169063095ea7b3906044016020604051808303816000875af1158015610af0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b149190612dea565b505050505050565b604051635d54e39560e01b81526001600160a01b0382811660048301526000917f0000000000000000000000007c2ca9d502f2409beceafa68e97a176ff805029f90911690635d54e39590602401602060405180830381865afa158015610b87573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bab9190612e0c565b90506001600160a01b038116610bd457604051637de7a71560e11b815260040160405180910390fd5b7f000000000000000000000000e37b8c83138caf12e57632d19c06eb561d47e4236001600160a01b0316816001600160a01b031603610cc55760405163db09c3fd60e01b81526001600160a01b0383811660048301527f000000000000000000000000e37b8c83138caf12e57632d19c06eb561d47e423169063db09c3fd90602401602060405180830381865afa158015610c73573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c979190612e0c565b90506001600160a01b038116610cc05760405163dc3810cb60e01b815260040160405180910390fd5b919050565b806001600160a01b031663ca0a8f226040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610d1f575060408051601f3d908101601f19168201909252610d1c91810190612dea565b60015b610d59573d808015610d4d576040519150601f19603f3d011682016040523d82523d6000602084013e610d52565b606091505b5050919050565b8015610df25760405163db09c3fd60e01b81526001600160a01b03848116600483015283169063db09c3fd90602401602060405180830381865afa158015610da5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dc99190612e0c565b91506001600160a01b038216610df25760405163dc3810cb60e01b815260040160405180910390fd5b50919050565b6000807f00000000000000000000000000000000000000000000000000000000000052085a8503019050803a02915082821115610e4c5760405163e1c4c60560e01b81528383036004820152602401610527565b5092915050565b6060838214610e755760405163fedad93960e01b815260040160405180910390fd5b6000846001600160401b03811115610e8f57610e8f612ada565b604051908082528060200260200182016040528015610eb8578160200160208202803683370190505b50905060005b858110156106f857848482818110610ed857610ed8612dd4565b9050602002016020810190610eed9190612a2e565b6001600160a01b03166338b51ce1888884818110610f0d57610f0d612dd4565b9050602002016020810190610f229190612a2e565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa158015610f66573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f8a9190612dea565b828281518110610f9c57610f9c612dd4565b91151560209283029190910190910152600101610ebe565b60405163025e1b9b60e31b81523360048201527f000000000000000000000000d998c35b7900b344bbbe6555cc11576942cf309d6001600160a01b0316906312f0dcd890602401602060405180830381865afa158015611018573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061103c9190612dea565b6110595760405163a812ea3160e01b815260040160405180910390fd5b7f000000000000000000000000e8e8041cb5e3158a0829a19e014ca1cf910985546000808061108a85870187613236565b92509250925080516000146110a2576110a2816114e6565b60006110b4838f8f8f8f8f8f8f61155c565b905060006110d28460078111156110cd576110cd612e29565b61162f565b806110f257506110f28460078111156110ed576110ed612e29565b611668565b9050600061111085600781111561110b5761110b612e29565b61168c565b9050811561112d576111268f8f8d8d8b866116e7565b9250611137565b61113787846119b6565b8f6001600160a01b0316336001600160a01b03167f7d233b2ffded63eccd0879cd7afc44e74118b788331db4d67730c6037d14269685856040516111879291909182521515602082015260400190565b60405180910390a380156111a15761119f8684610df8565b505b50505050505050505050505050505050565b6000546001600160a01b031633146111dd5760405162461bcd60e51b815260040161052790612d5e565b6001600160a01b0381166112425760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610527565b61124b8161146d565b50565b6000546001600160a01b031633146112785760405162461bcd60e51b815260040161052790612d5e565b61058b8282808060200260200160405190810160405280939291908181526020016000905b828210156112c9576112ba60408302860136819003810190612d93565b8152602001906001019061129d565b5050505050611a44565b60006001600160a01b03831615801590611352575082826040518163ffffffff1660e01b8152600401602060405180830381865afa158015611319573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061133d919061335b565b60e083901b6001600160e01b03199081169116145b90505b92915050565b60005b815181101561058b57600082828151811061137b5761137b612dd4565b6020026020010151600001519050600083838151811061139d5761139d612dd4565b602002602001015160200151905060006001600160a01b0316826001600160a01b031614806113d357506001600160a01b038116155b156113f157604051635d7aa74360e11b815260040160405180910390fd5b6001600160a01b0382811660008181526001602090815260409182902080546001600160a01b031916948616948517905581519283528201929092527f388056c710663a451dfea6adfe8ff11b52af5529b0a3d1c69ba90f1fc3f2dc56910160405180910390a1505080806114659061339b565b91505061135e565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b8151156114cc57815182602001fd5b8060405162461bcd60e51b815260040161052791906133b4565b60005b815181101561058b5761155482828151811061150757611507612dd4565b60200260200101516000015183838151811061152557611525612dd4565b60200260200101516020015184848151811061154357611543612dd4565b602002602001015160400151610973565b6001016114e9565b60006115738960078111156110cd576110cd612e29565b8061158e575061158e8960078111156110ed576110ed612e29565b156115a9576115a1338989898787611b56565b506000611623565b6115c38960078111156115be576115be612e29565b611bdc565b156115de576115d788888888888888611bff565b9050611623565b6115f88960078111156115f3576115f3612e29565b611c46565b1561160a576115d78888888686611c6a565b60405163596de99760e01b815260040160405180910390fd5b98975050505050505050565b6000600282600781111561164557611645612e29565b1480611355575060065b82600781111561166157611661612e29565b1492915050565b6000600382600781111561167e5761167e612e29565b14806113555750600761164f565b6000808260078111156116a1576116a1612e29565b14806116be575060018260078111156116bc576116bc612e29565b145b806116da575060028260078111156116d8576116d8612e29565b145b806113555750600361164f565b6000805b868110156119ab5785858281811061170557611705612dd4565b905060200201356000146119a357600088888381811061172757611727612dd4565b905060200201602081019061173c9190612a2e565b6040516370a0823160e01b81523060048201526001600160a01b0391909116906370a0823190602401602060405180830381865afa158015611782573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117a691906133c7565b90507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b03168989848181106117e4576117e4612dd4565b90506020020160208101906117f99190612a2e565b6001600160a01b03160361182157831561181257918201915b61181c85826119b6565b6119a1565b831561190357670de0b6b3a76400007f0000000000000000000000007c2ca9d502f2409beceafa68e97a176ff805029f6001600160a01b03166341976e098b8b8681811061187157611871612dd4565b90506020020160208101906118869190612a2e565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa1580156118ca573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ee91906133c7565b8202816118fd576118fd6133e0565b04830192505b88888381811061191557611915612dd4565b905060200201602081019061192a9190612a2e565b60405163a9059cbb60e01b81526001600160a01b03878116600483015260248201849052919091169063a9059cbb906044016020604051808303816000875af115801561197b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061199f9190612dea565b505b505b6001016116eb565b509695505050505050565b604051632e1a7d4d60e01b8152600481018290527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031690632e1a7d4d90602401600060405180830381600087803b158015611a1857600080fd5b505af1158015611a2c573d6000803e3d6000fd5b5061058b925050506001600160a01b03831682611d19565b60005b815181101561058b576000828281518110611a6457611a64612dd4565b60200260200101516000015190506000838381518110611a8657611a86612dd4565b602002602001015160200151905060006001600160a01b0316826001600160a01b03161480611abc57506001600160a01b038116155b15611ada5760405163acb22e5160e01b815260040160405180910390fd5b6001600160a01b0382811660008181526002602090815260409182902080546001600160a01b031916948616948517905581519283528201929092527f53db4a2342abba949712a6909b124e7452f4c84834894b2a75a377f5201ef610910160405180910390a150508080611b4e9061339b565b915050611a47565b60005b83811015611bd357828282818110611b7357611b73612dd4565b90506020020135600014611bcb57611bcb8787878785818110611b9857611b98612dd4565b9050602002016020810190611bad9190612a2e565b868686818110611bbf57611bbf612dd4565b90506020020135611e37565b600101611b59565b50505050505050565b600080826007811115611bf157611bf1612e29565b14806113555750600461164f565b600080611c0e8888888861206f565b90506000611c1e898987876120d8565b9050611c2e338b8b8b8989611b56565b611c3881836133f6565b9a9950505050505050505050565b60006001826007811115611c5c57611c5c612e29565b14806113555750600561164f565b6000611c78858585856120d8565b50611c87338787878787611b56565b6040516370a0823160e01b81523060048201527f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0316906370a0823190602401602060405180830381865afa158015611ceb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d0f91906133c7565b9695505050505050565b80471015611d695760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610527565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114611db6576040519150601f19603f3d011682016040523d82523d6000602084013e611dbb565b606091505b5050905080611e325760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d617920686176652072657665727465640000000000006064820152608401610527565b505050565b6040516001600160a01b038581166024830152604482018390526000919084169060640160408051601f198184030181529181526020820180516001600160e01b031663095ea7b360e01b17905251611e90919061321a565b6000604051808303816000865af19150503d8060008114611ecd576040519150601f19603f3d011682016040523d82523d6000602084013e611ed2565b606091505b5050905080611ef45760405163f4f9953760e01b815260040160405180910390fd5b60405163976ce49560e01b81526001600160a01b03848116600483015285811660248301526044820184905286169063976ce4959060640160408051808303816000875af1158015611f4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f6e919061340d565b505060405163bf27304160e01b81526001600160a01b03848116600483015286169063bf2730419060240160c060405180830381865afa158015611fb6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fda9190613431565b60409081015190516370a0823160e01b81526001600160a01b038681166004830152909116906370a0823190602401602060405180830381865afa158015612026573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061204a91906133c7565b1561206857604051639e703a0560e01b815260040160405180910390fd5b5050505050565b6000805b848110156120cf576120c386868381811061209057612090612dd4565b90506020020160208101906120a59190612a2e565b8585848181106120b7576120b7612dd4565b90506020020135612159565b90910190600101612073565b50949350505050565b6000805b848110156120cf578383828181106120f6576120f6612dd4565b905060200201356000146121515761214c86868381811061211957612119612dd4565b905060200201602081019061212e9190612a2e565b85858481811061214057612140612dd4565b90506020020135612457565b820191505b6001016120dc565b60007f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc282158061219a5750806001600160a01b0316846001600160a01b0316145b156121a85782915050611355565b6001600160a01b03808516600090815260026020526040902054168015612278576040516001600160a01b03861660248201526044810185905260009061224590839060640160408051601f19818403018152918152602080830180516001600160e01b03166321ebbf0160e01b179052815180830190925260138252721d1bddd85c991cd3985d1a5d9951985a5b1959606a1b908201526127b2565b90506000808280602001905181019061225e91906134c5565b9150915061226c8282612159565b95505050505050611355565b60008061228487612836565b915091506000876001600160a01b0316826001600160a01b031663db36eb2e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156122d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122f69190612e0c565b6040516001600160a01b0390911660248201526044810189905260640160408051601f198184030181529181526020820180516001600160e01b031663095ea7b360e01b17905251612348919061321a565b6000604051808303816000865af19150503d8060008114612385576040519150601f19603f3d011682016040523d82523d6000602084013e61238a565b606091505b50509050806123ac576040516340b27c2160e11b815260040160405180910390fd5b6040516001600160a01b03808a16602483018190528188166044840152606483018a9052908516608483015260a482015260009060c40160408051601f19818403018152918152602080830180516001600160e01b0316639cffaf6f60e01b1790528151808301909252600c82526b39bbb0b820b6b7bab73a24b760a11b9082015290915060009061244190859084906127b2565b905080806020019051810190611c3891906133c7565b60007f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28215806124985750836001600160a01b0316816001600160a01b0316145b156124a65782915050611355565b6001600160a01b0380851660009081526002602052604090205416801561259d576040516001600160a01b03861660248201526044810185905260009061254290839060640160408051601f19818403018152918152602080830180516001600160e01b03166337523bed60e11b179052815180830190925260128252711d1bddd85c991cd05cdcd95d11985a5b195960721b908201526127b2565b90506000808280602001905181019061255b91906134c5565b91509150876001600160a01b0316826001600160a01b0316146125915760405163245feacb60e01b815260040160405180910390fd5b94506113559350505050565b6000806125a987612836565b915091506000816001600160a01b031663db36eb2e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156125ed573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126119190612e0c565b60405163095ea7b360e01b81526001600160a01b03808316600483015260001960248301529192509086169063095ea7b3906044016020604051808303816000875af1158015612665573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126899190612dea565b506040516001600160a01b038087166024830152808a1660448301819052606483018a9052908516608483015260a482015260009060c40160408051601f19818403018152918152602080830180516001600160e01b03166305a319ad60e21b1790528151808301909252601382527214ddd85c105b5bdd5b9d13dd5d11985a5b1959606a1b9082015290915060009061272690859084906127b2565b60405163095ea7b360e01b81526001600160a01b038581166004830152600060248301529192509088169063095ea7b3906044016020604051808303816000875af1158015612779573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061279d9190612dea565b5080806020019051810190611c3891906133c7565b60606000846001600160a01b0316846040516127ce919061321a565b600060405180830381855af49150503d8060008114612809576040519150601f19603f3d011682016040523d82523d6000602084013e61280e565b606091505b509250905080158061281f57508151155b1561282e5761282e82846114bd565b509392505050565b600080600061284484610b1c565b905060006128518261285d565b91959194509092505050565b6001600160a01b038082166000908152600160205260408120549091168061135557604051636ce6ae9b60e11b815260040160405180910390fd5b60008083601f8401126128aa57600080fd5b5081356001600160401b038111156128c157600080fd5b6020830191508360208260061b85010111156128dc57600080fd5b9250929050565b600080602083850312156128f657600080fd5b82356001600160401b0381111561290c57600080fd5b61291885828601612898565b90969095509350505050565b60008083601f84011261293657600080fd5b5081356001600160401b0381111561294d57600080fd5b6020830191508360208260051b85010111156128dc57600080fd5b6000806000806040858703121561297e57600080fd5b84356001600160401b038082111561299557600080fd5b6129a188838901612924565b909650945060208701359150808211156129ba57600080fd5b506129c787828801612924565b95989497509550505050565b6020808252825182820181905260009190848201906040850190845b81811015612a0d5783511515835292840192918401916001016129ef565b50909695505050505050565b6001600160a01b038116811461124b57600080fd5b600060208284031215612a4057600080fd5b8135612a4b81612a19565b9392505050565b803560088110610cc057600080fd5b600080600080600060808688031215612a7957600080fd5b8535612a8481612a19565b94506020860135612a9481612a19565b9350612aa260408701612a52565b925060608601356001600160401b03811115612abd57600080fd5b612ac988828901612924565b969995985093965092949392505050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612b1257612b12612ada565b60405290565b604051606081016001600160401b0381118282101715612b1257612b12612ada565b604051601f8201601f191681016001600160401b0381118282101715612b6257612b62612ada565b604052919050565b600082601f830112612b7b57600080fd5b81356001600160401b03811115612b9457612b94612ada565b612ba7601f8201601f1916602001612b3a565b818152846020838601011115612bbc57600080fd5b816020850160208301376000918101602001919091529392505050565b600080600060608486031215612bee57600080fd5b8335612bf981612a19565b92506020840135612c0981612a19565b915060408401356001600160401b03811115612c2457600080fd5b612c3086828701612b6a565b9150509250925092565b60008060408385031215612c4d57600080fd5b50508035926020909101359150565b600080600080600080600080600060a08a8c031215612c7a57600080fd5b8935612c8581612a19565b985060208a01356001600160401b0380821115612ca157600080fd5b612cad8d838e01612924565b909a50985060408c0135915080821115612cc657600080fd5b612cd28d838e01612924565b909850965060608c0135915080821115612ceb57600080fd5b612cf78d838e01612924565b909650945060808c0135915080821115612d1057600080fd5b818c0191508c601f830112612d2457600080fd5b813581811115612d3357600080fd5b8d6020828501011115612d4557600080fd5b6020830194508093505050509295985092959850929598565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b600060408284031215612da557600080fd5b612dad612af0565b8235612db881612a19565b81526020830135612dc881612a19565b60208201529392505050565b634e487b7160e01b600052603260045260246000fd5b600060208284031215612dfc57600080fd5b81518015158114612a4b57600080fd5b600060208284031215612e1e57600080fd5b8151612a4b81612a19565b634e487b7160e01b600052602160045260246000fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60006060808301878452602060088810612e9257634e487b7160e01b600052602160045260246000fd5b8781860152604083818701528287845260808701905060808860051b88010193508860005b89811015612f7157888603607f190183528135368c9003605e19018112612edd57600080fd5b8b018035612eea81612a19565b6001600160a01b0390811688528187013590612f0582612a19565b16878701528085013536829003601e19018112612f2157600080fd5b810180356001600160401b03811115612f3957600080fd5b803603831315612f4857600080fd5b89878a0152612f5c8a8a01828a8501612e3f565b98505050928501925090840190600101612eb7565b50939b9a5050505050505050505050565b60005b83811015612f9d578181015183820152602001612f85565b83811115612fac576000848401525b50505050565b60008151808452612fca816020860160208601612f82565b601f01601f19169290920160200192915050565b604080825283519082018190526000906020906060840190828701845b828110156130205781516001600160a01b031684529284019290840190600101612ffb565b50505083810382850152611d0f8186612fb2565b60006001600160401b0382111561304d5761304d612ada565b5060051b60200190565b600082601f83011261306857600080fd5b8151602061307d61307883613034565b612b3a565b828152600592831b850182019282820191908785111561309c57600080fd5b8387015b8581101561312f5780516001600160401b038111156130bf5760008081fd5b8801603f81018a136130d15760008081fd5b8581015160406130e361307883613034565b82815291851b8301810191888101908d8411156131005760008081fd5b938201935b8385101561311e57845182529389019390890190613105565b8852505050938501935084016130a0565b5090979650505050505050565b60008060006060848603121561315157600080fd5b83516001600160401b038082111561316857600080fd5b818601915086601f83011261317c57600080fd5b8151602061318c61307883613034565b82815260059290921b8401810191818101908a8411156131ab57600080fd5b948201945b838610156131d25785516131c381612a19565b825294820194908201906131b0565b918901519197509093505050808211156131eb57600080fd5b6131f787838801613057565b9350604086015191508082111561320d57600080fd5b50612c3086828701613057565b6000825161322c818460208701612f82565b9190910192915050565b60008060006060848603121561324b57600080fd5b83359250602061325c818601612a52565b925060408501356001600160401b038082111561327857600080fd5b818701915087601f83011261328c57600080fd5b813561329a61307882613034565b81815260059190911b8301840190848101908a8311156132b957600080fd5b8585015b8381101561334a578035858111156132d55760008081fd5b86016060818e03601f190112156132ec5760008081fd5b6132f4612b18565b8882013561330181612a19565b8152604082013561331181612a19565b818a01526060820135878111156133285760008081fd5b6133368f8b83860101612b6a565b6040830152508452509186019186016132bd565b508096505050505050509250925092565b60006020828403121561336d57600080fd5b81516001600160e01b031981168114612a4b57600080fd5b634e487b7160e01b600052601160045260246000fd5b6000600182016133ad576133ad613385565b5060010190565b6020815260006113526020830184612fb2565b6000602082840312156133d957600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008282101561340857613408613385565b500390565b6000806040838503121561342057600080fd5b505080516020909101519092909150565b600060c0828403121561344357600080fd5b60405160c081018181106001600160401b038211171561346557613465612ada565b604052825161347381612a19565b8152602083015161348381612a19565b6020820152604083015161349681612a19565b80604083015250606083015160608201526080830151608082015260a083015160a08201528091505092915050565b600080604083850312156134d857600080fd5b82516134e381612a19565b602093909301519294929350505056fea26469706673582212200571bd96568f165c93fd3af8fe61516911643ebd00a9f7a1a3f9654bff9121d864736f6c634300080d0033
Verified Source Code Full Match
Compiler: v0.8.13+commit.abaa5c0e
EVM: london
Optimization: Yes (200 runs)
SiloLens.sol 469 lines
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.13; import "./interfaces/IBaseSilo.sol"; import "./interfaces/ISilo.sol"; import "./lib/EasyMathV2.sol"; import "./lib/Ping.sol"; import "./lib/SolvencyV2.sol"; /// @title SiloLens /// @notice Utility contract that simplifies reading data from Silo protocol contracts /// @custom:security-contact [email protected] contract SiloLens { using EasyMathV2 for uint256; ISiloRepository immutable public siloRepository; error InvalidRepository(); error UserIsZero(); constructor (ISiloRepository _siloRepo) { if (!Ping.pong(_siloRepo.siloRepositoryPing)) revert InvalidRepository(); siloRepository = _siloRepo; } /// @dev calculates solvency using SolvencyV2 library /// @param _silo Silo address from which to read data /// @param _user wallet address /// @return true if solvent, false otherwise function isSolvent(ISilo _silo, address _user) external view returns (bool) { if (_user == address(0)) revert UserIsZero(); (address[] memory assets, IBaseSilo.AssetStorage[] memory assetsStates) = _silo.getAssetsWithState(); (uint256 userLTV, uint256 liquidationThreshold) = SolvencyV2.calculateLTVs( SolvencyV2.SolvencyParams( siloRepository, ISilo(address(this)), assets, assetsStates, _user ), SolvencyV2.TypeofLTV.LiquidationThreshold ); return userLTV <= liquidationThreshold; } /// @dev Amount of token that is available for borrowing. /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @return Silo liquidity function liquidity(ISilo _silo, address _asset) external view returns (uint256) { return ERC20(_asset).balanceOf(address(_silo)) - _silo.assetStorage(_asset).collateralOnlyDeposits; } /// @notice Get amount of asset token that has been deposited to Silo /// @dev It reads directly from storage so interest generated between last update and now is not taken for account /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @return amount of all deposits made for given asset function totalDeposits(ISilo _silo, address _asset) external view returns (uint256) { return _silo.utilizationData(_asset).totalDeposits; } /// @notice Get amount of asset token that has been deposited to Silo with option "collateralOnly" /// @dev It reads directly from storage so interest generated between last update and now is not taken for account /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @return amount of all "collateralOnly" deposits made for given asset function collateralOnlyDeposits(ISilo _silo, address _asset) external view returns (uint256) { return _silo.assetStorage(_asset).collateralOnlyDeposits; } /// @notice Get amount of asset that has been borrowed /// @dev It reads directly from storage so interest generated between last update and now is not taken for account /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @return amount of asset that has been borrowed function totalBorrowAmount(ISilo _silo, address _asset) external view returns (uint256) { return _silo.assetStorage(_asset).totalBorrowAmount; } /// @notice Get amount of fees earned by protocol to date /// @dev It reads directly from storage so interest generated between last update and now is not taken for account /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @return amount of fees earned by protocol to date function protocolFees(ISilo _silo, address _asset) external view returns (uint256) { return _silo.interestData(_asset).protocolFees; } /// @notice Returns Loan-To-Value for an account /// @dev Each Silo has multiple asset markets (bridge assets + unique asset). This function calculates /// a sum of all deposits and all borrows denominated in quote token. Returns fraction between borrow value /// and deposit value with 18 decimals. /// @param _silo Silo address from which to read data /// @param _user wallet address for which LTV is calculated /// @return userLTV user current LTV with 18 decimals function getUserLTV(ISilo _silo, address _user) external view returns (uint256 userLTV) { (address[] memory assets, ISilo.AssetStorage[] memory assetsStates) = _silo.getAssetsWithState(); (userLTV, ) = SolvencyV2.calculateLTVs( SolvencyV2.SolvencyParams( siloRepository, _silo, assets, assetsStates, _user ), SolvencyV2.TypeofLTV.MaximumLTV ); } /// @notice Get totalSupply of debt token /// @dev Debt token represents a share in total debt of given asset /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @return totalSupply of debt token function totalBorrowShare(ISilo _silo, address _asset) external view returns (uint256) { return _silo.assetStorage(_asset).debtToken.totalSupply(); } /// @notice Calculates current borrow amount for user with interest /// @dev Interest is calculated based on the provided timestamp with is expected to be current time. /// @param _silo Silo address from which to read data /// @param _asset token address for which calculation are done /// @param _user account for which calculation are done /// @param _timestamp timestamp used for interest calculations /// @return total amount of asset user needs to repay at provided timestamp function getBorrowAmount(ISilo _silo, address _asset, address _user, uint256 _timestamp) external view returns (uint256) { return SolvencyV2.getUserBorrowAmount( _silo.assetStorage(_asset), _user, SolvencyV2.getRcomp(_silo, siloRepository, _asset, _timestamp) ); } /// @notice Get debt token balance of a user /// @dev Debt token represents a share in total debt of given asset. This method calls balanceOf(_user) /// on that token. /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @param _user wallet address for which to read data /// @return balance of debt token of given user function borrowShare(ISilo _silo, address _asset, address _user) external view returns (uint256) { return _silo.assetStorage(_asset).debtToken.balanceOf(_user); } /// @notice Get underlying balance of all deposits of given token of given user including "collateralOnly" /// deposits /// @dev It reads directly from storage so interest generated between last update and now is not taken for account /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @param _user wallet address for which to read data /// @return balance of underlying tokens for the given user function collateralBalanceOfUnderlying(ISilo _silo, address _asset, address _user) external view returns (uint256) { ISilo.AssetStorage memory _state = _silo.assetStorage(_asset); // Overflow shouldn't happen if the underlying token behaves correctly, as the total supply of underlying // tokens can't overflow by definition unchecked { return balanceOfUnderlying(_state.totalDeposits, _state.collateralToken, _user) + balanceOfUnderlying(_state.collateralOnlyDeposits, _state.collateralOnlyToken, _user); } } /// @notice Get amount of debt of underlying token for given user /// @dev It reads directly from storage so interest generated between last update and now is not taken for account /// @param _silo Silo address from which to read data /// @param _asset asset address for which to read data /// @param _user wallet address for which to read data /// @return balance of underlying token owed function debtBalanceOfUnderlying(ISilo _silo, address _asset, address _user) external view returns (uint256) { ISilo.AssetStorage memory _state = _silo.assetStorage(_asset); return balanceOfUnderlying(_state.totalBorrowAmount, _state.debtToken, _user); } /// @notice Calculate value of collateral asset for user /// @dev It dynamically adds interest earned. Takes for account collateral only deposits as well. /// @param _silo Silo address from which to read data /// @param _user account for which calculation are done /// @param _asset token address for which calculation are done /// @return value of collateral denominated in quote token with 18 decimal function calculateCollateralValue(ISilo _silo, address _user, address _asset) external view returns (uint256) { IPriceProvidersRepository priceProviderRepo = siloRepository.priceProvidersRepository(); ISilo.AssetStorage memory assetStorage = _silo.assetStorage(_asset); uint256 assetPrice = priceProviderRepo.getPrice(_asset); uint8 assetDecimals = ERC20(_asset).decimals(); uint256 userCollateralTokenBalance = assetStorage.collateralToken.balanceOf(_user); uint256 userCollateralOnlyTokenBalance = assetStorage.collateralOnlyToken.balanceOf(_user); uint256 assetAmount = SolvencyV2.getUserCollateralAmount( assetStorage, userCollateralTokenBalance, userCollateralOnlyTokenBalance, SolvencyV2.getRcomp(_silo, siloRepository, _asset, block.timestamp), siloRepository ); return assetAmount.toValue(assetPrice, assetDecimals); } /// @notice Calculate value of borrowed asset by user /// @dev It dynamically adds interest earned to borrowed amount /// @param _silo Silo address from which to read data /// @param _user account for which calculation are done /// @param _asset token address for which calculation are done /// @return value of debt denominated in quote token with 18 decimal function calculateBorrowValue(ISilo _silo, address _user, address _asset) external view returns (uint256) { IPriceProvidersRepository priceProviderRepo = siloRepository.priceProvidersRepository(); uint256 assetPrice = priceProviderRepo.getPrice(_asset); uint256 assetDecimals = ERC20(_asset).decimals(); uint256 rcomp = SolvencyV2.getRcomp(_silo, siloRepository, _asset, block.timestamp); uint256 borrowAmount = SolvencyV2.getUserBorrowAmount(_silo.assetStorage(_asset), _user, rcomp); return borrowAmount.toValue(assetPrice, assetDecimals); } /// @notice Get combined liquidation threshold for a user /// @dev Methodology for calculating liquidation threshold is as follows. Each Silo is combined form multiple /// assets (bridge assets + unique asset). Each of these assets may have different liquidation threshold. /// That means effective liquidation threshold must be calculated per asset based on current deposits and /// borrows of given account. /// @param _silo Silo address from which to read data /// @param _user wallet address for which to read data /// @return liquidationThreshold liquidation threshold of given user function getUserLiquidationThreshold(ISilo _silo, address _user) external view returns (uint256 liquidationThreshold) { (address[] memory assets, ISilo.AssetStorage[] memory assetsStates) = _silo.getAssetsWithState(); liquidationThreshold = SolvencyV2.calculateLTVLimit( SolvencyV2.SolvencyParams( siloRepository, _silo, assets, assetsStates, _user ), SolvencyV2.TypeofLTV.LiquidationThreshold ); } /// @notice Get combined maximum Loan-To-Value for a user /// @dev Methodology for calculating maximum LTV is as follows. Each Silo is combined form multiple assets /// (bridge assets + unique asset). Each of these assets may have different maximum Loan-To-Value for /// opening borrow position. That means effective maximum LTV must be calculated per asset based on /// current deposits and borrows of given account. /// @param _silo Silo address from which to read data /// @param _user wallet address for which to read data /// @return maximumLTV Maximum Loan-To-Value of given user function getUserMaximumLTV(ISilo _silo, address _user) external view returns (uint256 maximumLTV) { (address[] memory assets, ISilo.AssetStorage[] memory assetsStates) = _silo.getAssetsWithState(); maximumLTV = SolvencyV2.calculateLTVLimit( SolvencyV2.SolvencyParams( siloRepository, _silo, assets, assetsStates, _user ), SolvencyV2.TypeofLTV.MaximumLTV ); } /// @notice Check if user is in debt /// @param _silo Silo address from which to read data /// @param _user wallet address for which to read data /// @return TRUE if user borrowed any amount of any asset, otherwise FALSE function inDebt(ISilo _silo, address _user) external view returns (bool) { address[] memory allAssets = _silo.getAssets(); for (uint256 i; i < allAssets.length;) { if (_silo.assetStorage(allAssets[i]).debtToken.balanceOf(_user) != 0) return true; unchecked { i++; } } return false; } /// @notice Check if user has position (debt or borrow) in any asset /// @param _silo Silo address from which to read data /// @param _user wallet address for which to read data /// @return TRUE if user has position (debt or borrow) in any asset function hasPosition(ISilo _silo, address _user) external view returns (bool) { (, ISilo.AssetStorage[] memory assetsStorage) = _silo.getAssetsWithState(); for (uint256 i; i < assetsStorage.length; i++) { if (assetsStorage[i].debtToken.balanceOf(_user) != 0) return true; if (assetsStorage[i].collateralToken.balanceOf(_user) != 0) return true; if (assetsStorage[i].collateralOnlyToken.balanceOf(_user) != 0) return true; } return false; } /// @notice Calculates fraction between borrowed amount and the current liquidity of tokens for given asset /// denominated in percentage /// @dev Utilization is calculated current values in storage so it does not take for account earned /// interest and ever-increasing total borrow amount. It assumes `Model.DP()` = 100%. /// @param _silo Silo address from which to read data /// @param _asset asset address /// @return utilization value function getUtilization(ISilo _silo, address _asset) external view returns (uint256) { ISilo.UtilizationData memory data = ISilo(_silo).utilizationData(_asset); return EasyMathV2.calculateUtilization( getModel(_silo, _asset).DP(), data.totalDeposits, data.totalBorrowAmount ); } /// @notice Yearly interest rate for depositing asset token, dynamically calculated for current block timestamp /// @param _silo Silo address from which to read data /// @param _asset asset address /// @return APY with 18 decimals function depositAPY(ISilo _silo, address _asset) external view returns (uint256) { uint256 dp = getModel(_silo, _asset).DP(); // amount of deposits in asset decimals uint256 totalDepositsAmount = totalDepositsWithInterest(_silo, _asset); if (totalDepositsAmount == 0) return 0; // amount of debt generated per year in asset decimals uint256 generatedDebtAmount = totalBorrowAmountWithInterest(_silo, _asset) * borrowAPY(_silo, _asset) / dp; return generatedDebtAmount * SolvencyV2._PRECISION_DECIMALS / totalDepositsAmount; } /// @notice Calculate amount of entry fee for given amount /// @param _amount amount for which to calculate fee /// @return Amount of token fee to be paid function calcFee(uint256 _amount) external view returns (uint256) { uint256 entryFee = siloRepository.entryFee(); if (entryFee == 0) return 0; // no fee unchecked { // If we overflow on multiplication it should not revert tx, we will get lower fees return _amount * entryFee / SolvencyV2._PRECISION_DECIMALS; } } /// @dev Method for sanity check /// @return always true function lensPing() external pure returns (bytes4) { return this.lensPing.selector; } /// @notice Yearly interest rate for borrowing asset token, dynamically calculated for current block timestamp /// @param _silo Silo address from which to read data /// @param _asset asset address /// @return APY with 18 decimals function borrowAPY(ISilo _silo, address _asset) public view returns (uint256) { return getModel(_silo, _asset).getCurrentInterestRate(address(_silo), _asset, block.timestamp); } /// @notice returns total deposits with interest dynamically calculated at current block timestamp /// @param _asset asset address /// @return _totalDeposits total deposits amount with interest function totalDepositsWithInterest(ISilo _silo, address _asset) public view returns (uint256 _totalDeposits) { uint256 rcomp = getModel(_silo, _asset).getCompoundInterestRate(address(_silo), _asset, block.timestamp); uint256 protocolShareFee = siloRepository.protocolShareFee(); ISilo.UtilizationData memory data = _silo.utilizationData(_asset); return SolvencyV2.totalDepositsWithInterest( data.totalDeposits, data.totalBorrowAmount, protocolShareFee, rcomp ); } /// @notice Calculates current deposit (with interest) for user /// Collateral only deposits are not counted here. To get collateral only deposit call: /// `_silo.assetStorage(_asset).collateralOnlyDeposits` /// @dev Interest is calculated based on the provided timestamp with is expected to be current time. /// @param _silo Silo address from which to read data /// @param _asset token address for which calculation are done /// @param _user account for which calculation are done /// @param _timestamp timestamp used for interest calculations /// @return totalUserDeposits amount of asset user posses function getDepositAmount(ISilo _silo, address _asset, address _user, uint256 _timestamp) public view returns (uint256 totalUserDeposits) { ISilo.AssetStorage memory data = _silo.assetStorage(_asset); uint256 share = data.collateralToken.balanceOf(_user); if (share == 0) { return 0; } uint256 rcomp = getModel(_silo, _asset).getCompoundInterestRate(address(_silo), _asset, _timestamp); uint256 protocolShareFee = siloRepository.protocolShareFee(); uint256 assetTotalDeposits = SolvencyV2.totalDepositsWithInterest( data.totalDeposits, data.totalBorrowAmount, protocolShareFee, rcomp ); return share.toAmount(assetTotalDeposits, data.collateralToken.totalSupply()); } /// @notice returns total borrow amount with interest dynamically calculated at current block timestamp /// @param _asset asset address /// @return _totalBorrowAmount total deposits amount with interest function totalBorrowAmountWithInterest(ISilo _silo, address _asset) public view returns (uint256 _totalBorrowAmount) { uint256 rcomp = SolvencyV2.getRcomp(_silo, siloRepository, _asset, block.timestamp); ISilo.UtilizationData memory data = _silo.utilizationData(_asset); return SolvencyV2.totalBorrowAmountWithInterest(data.totalBorrowAmount, rcomp); } /// @notice Get underlying balance of collateral or debt token /// @dev You can think about debt and collateral tokens as cToken in compound. They represent ownership of /// debt or collateral in given Silo. This method converts that ownership to exact amount of underlying token. /// @param _assetTotalDeposits Total amount of assets that has been deposited or borrowed. For collateral token, /// use `totalDeposits` to get this value. For debt token, use `totalBorrowAmount` to get this value. /// @param _shareToken share token address. It's the collateral and debt share token address. You can find /// these addresses in: /// - `ISilo.AssetStorage.collateralToken` /// - `ISilo.AssetStorage.collateralOnlyToken` /// - `ISilo.AssetStorage.debtToken` /// @param _user wallet address for which to read data /// @return balance of underlying token deposited or borrowed of given user function balanceOfUnderlying(uint256 _assetTotalDeposits, IShareToken _shareToken, address _user) public view returns (uint256) { uint256 share = _shareToken.balanceOf(_user); return share.toAmount(_assetTotalDeposits, _shareToken.totalSupply()); } /// @dev gets interest rates model object /// @param _silo Silo address from which to read data /// @param _asset asset for which to calculate interest rate /// @return IInterestRateModel interest rates model object function getModel(ISilo _silo, address _asset) public view returns (IInterestRateModel) { return IInterestRateModel(siloRepository.getInterestRateModel(address(_silo), _asset)); } }
Ping.sol 9 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6 <0.9.0;
library Ping {
function pong(function() external pure returns(bytes4) pingFunction) internal pure returns (bool) {
return pingFunction.address != address(0) && pingFunction.selector == pingFunction();
}
}
EasyMath.sol 99 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
library EasyMath {
error ZeroAssets();
error ZeroShares();
function toShare(uint256 amount, uint256 totalAmount, uint256 totalShares) internal pure returns (uint256) {
if (totalShares == 0 || totalAmount == 0) {
return amount;
}
uint256 result = amount * totalShares / totalAmount;
// Prevent rounding error
if (result == 0 && amount != 0) {
revert ZeroShares();
}
return result;
}
function toShareRoundUp(uint256 amount, uint256 totalAmount, uint256 totalShares) internal pure returns (uint256) {
if (totalShares == 0 || totalAmount == 0) {
return amount;
}
uint256 numerator = amount * totalShares;
uint256 result = numerator / totalAmount;
// Round up
if (numerator % totalAmount != 0) {
result += 1;
}
return result;
}
function toAmount(uint256 share, uint256 totalAmount, uint256 totalShares) internal pure returns (uint256) {
if (totalShares == 0 || totalAmount == 0) {
return 0;
}
uint256 result = share * totalAmount / totalShares;
// Prevent rounding error
if (result == 0 && share != 0) {
revert ZeroAssets();
}
return result;
}
function toAmountRoundUp(uint256 share, uint256 totalAmount, uint256 totalShares) internal pure returns (uint256) {
if (totalShares == 0 || totalAmount == 0) {
return 0;
}
uint256 numerator = share * totalAmount;
uint256 result = numerator / totalShares;
// Round up
if (numerator % totalShares != 0) {
result += 1;
}
return result;
}
function toValue(uint256 _assetAmount, uint256 _assetPrice, uint256 _assetDecimals)
internal
pure
returns (uint256)
{
return _assetAmount * _assetPrice / 10 ** _assetDecimals;
}
function sum(uint256[] memory _numbers) internal pure returns (uint256 s) {
for(uint256 i; i < _numbers.length; i++) {
s += _numbers[i];
}
}
/// @notice Calculates fraction between borrowed and deposited amount of tokens denominated in percentage
/// @dev It assumes `_dp` = 100%.
/// @param _dp decimal points used by model
/// @param _totalDeposits current total deposits for assets
/// @param _totalBorrowAmount current total borrows for assets
/// @return utilization value
function calculateUtilization(uint256 _dp, uint256 _totalDeposits, uint256 _totalBorrowAmount)
internal
pure
returns (uint256)
{
if (_totalDeposits == 0 || _totalBorrowAmount == 0) return 0;
return _totalBorrowAmount * _dp / _totalDeposits;
}
}
EasyMathV2.sol 125 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
/// @dev EasyMathV2 is optimised version of EasyMath, many places was `unchecked` for lower gas cost.
/// There is also fixed version of `calculateUtilization()` method.
library EasyMathV2 {
error ZeroAssets();
error ZeroShares();
function toShare(uint256 amount, uint256 totalAmount, uint256 totalShares)
internal
pure
returns (uint256 result)
{
if (totalShares == 0 || totalAmount == 0) {
return amount;
}
result = amount * totalShares;
// totalAmount is never 0 based on above check, so we can uncheck
unchecked { result /= totalAmount; }
// Prevent rounding error
if (result == 0 && amount != 0) {
revert ZeroShares();
}
}
function toShareRoundUp(uint256 amount, uint256 totalAmount, uint256 totalShares)
internal
pure
returns (uint256 result)
{
if (totalShares == 0 || totalAmount == 0) {
return amount;
}
uint256 numerator = amount * totalShares;
// totalAmount is not 0, so it is safe to uncheck
unchecked { result = numerator / totalAmount; }
// Round up
if (numerator % totalAmount != 0) {
unchecked { result += 1; }
}
}
function toAmount(uint256 share, uint256 totalAmount, uint256 totalShares)
internal
pure
returns (uint256 result)
{
if (totalShares == 0 || totalAmount == 0) {
return 0;
}
result = share * totalAmount;
// totalShares are not 0, so we can uncheck
unchecked { result /= totalShares; }
// Prevent rounding error
if (result == 0 && share != 0) {
revert ZeroAssets();
}
}
function toAmountRoundUp(uint256 share, uint256 totalAmount, uint256 totalShares)
internal
pure
returns (uint256 result)
{
if (totalShares == 0 || totalAmount == 0) {
return 0;
}
uint256 numerator = share * totalAmount;
// totalShares are not 0, based on above check, so we can uncheck
unchecked { result = numerator / totalShares; }
// Round up
if (numerator % totalShares != 0) {
unchecked { result += 1; }
}
}
function toValue(uint256 _assetAmount, uint256 _assetPrice, uint256 _assetDecimals)
internal
pure
returns (uint256 value)
{
value = _assetAmount * _assetPrice;
// power of 10 can not be 0, so we can uncheck
unchecked { value /= 10 ** _assetDecimals; }
}
function sum(uint256[] memory _numbers) internal pure returns (uint256 s) {
for(uint256 i; i < _numbers.length;) {
s += _numbers[i];
unchecked { i++; }
}
}
/// @notice Calculates fraction between borrowed and deposited amount of tokens denominated in percentage
/// @dev It assumes `_dp` = 100%.
/// @param _dp decimal points used by model
/// @param _totalDeposits current total deposits for assets
/// @param _totalBorrowAmount current total borrows for assets
/// @return utilization value, capped to 100%
/// Limiting utilisation ratio by 100% max will allows us to perform better interest rate computations
/// and should not affect any other part of protocol.
function calculateUtilization(uint256 _dp, uint256 _totalDeposits, uint256 _totalBorrowAmount)
internal
pure
returns (uint256 utilization)
{
if (_totalDeposits == 0 || _totalBorrowAmount == 0) return 0;
utilization = _totalBorrowAmount * _dp;
// _totalDeposits is not 0 based on above check, so it is safe to uncheck this division
unchecked { utilization /= _totalDeposits; }
// cap at 100%
if (utilization > _dp) utilization = _dp;
}
}
SolvencyV2.sol 398 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../interfaces/IPriceProvidersRepository.sol";
import "../interfaces/ISilo.sol";
import "../interfaces/IInterestRateModel.sol";
import "../interfaces/ISiloRepository.sol";
import "./EasyMath.sol";
library SolvencyV2 {
using EasyMath for uint256;
/// @notice
/// MaximumLTV - Maximum Loan-to-Value ratio represents the maximum borrowing power of all user's collateral
/// positions in a Silo
/// LiquidationThreshold - Liquidation Threshold represents the threshold at which all user's borrow positions
/// in a Silo will be considered under collateralized and subject to liquidation
enum TypeofLTV { MaximumLTV, LiquidationThreshold }
error DifferentArrayLength();
error UnsupportedLTVType();
struct SolvencyParams {
/// @param siloRepository SiloRepository address
ISiloRepository siloRepository;
/// @param silo Silo address
ISilo silo;
/// @param assets array with assets
address[] assets;
/// @param assetStates array of states for each asset, where index match the `assets` index
ISilo.AssetStorage[] assetStates;
/// @param user wallet address for which to read debt
address user;
}
/// @dev is value that used for integer calculations and decimal points for utilization ratios, LTV, protocol fees
uint256 internal constant _PRECISION_DECIMALS = 1e18;
uint256 internal constant _INFINITY = type(uint256).max;
/// @notice Returns current user LTV and second LTV chosen in params
/// @dev This function is optimized for protocol use. In some cases there is no need to keep the calculation
/// going and predefined results can be returned.
/// @param _params `SolvencyV2.SolvencyParams` struct with needed params for calculation
/// @param _secondLtvType type of LTV to be returned as second value
/// @return currentUserLTV Loan-to-Value ratio represents current user's proportion of debt to collateral
/// @return secondLTV second type of LTV which depends on _secondLtvType, zero is returned if the value of the loan
/// or the collateral are zero
function calculateLTVs(SolvencyParams memory _params, TypeofLTV _secondLtvType)
internal
view
returns (uint256 currentUserLTV, uint256 secondLTV)
{
uint256[] memory totalBorrowAmounts = getBorrowAmounts(_params);
// this return avoids eg. additional checks on withdraw, when user did not borrow any asset
if (EasyMath.sum(totalBorrowAmounts) == 0) return (0, 0);
IPriceProvidersRepository priceProvidersRepository = _params.siloRepository.priceProvidersRepository();
uint256[] memory borrowValues = convertAmountsToValues(
priceProvidersRepository,
_params.assets,
totalBorrowAmounts
);
// value of user's total debt
uint256 borrowTotalValue = EasyMath.sum(borrowValues);
if (borrowTotalValue == 0) return (0, 0);
uint256[] memory collateralValues = getUserCollateralValues(priceProvidersRepository, _params);
// value of user's collateral
uint256 collateralTotalValue = EasyMath.sum(collateralValues);
if (collateralTotalValue == 0) return (_INFINITY, 0);
// value of theoretical debt user can have depending on TypeofLTV
uint256 borrowAvailableTotalValue = _getTotalAvailableToBorrowValue(
_params.siloRepository,
address(_params.silo),
_params.assets,
_secondLtvType,
collateralValues
);
currentUserLTV = borrowTotalValue * _PRECISION_DECIMALS / collateralTotalValue;
// one of SolvencyV2.TypeofLTV
secondLTV = borrowAvailableTotalValue * _PRECISION_DECIMALS / collateralTotalValue;
}
/// @notice Calculates chosen LTV limit
/// @dev This function should be used by external actors like SiloLens and UI/subgraph. `calculateLTVs` is
/// optimized for protocol use and may not return second LVT calculation when they are not needed.
/// @param _params `SolvencyV2.SolvencyParams` struct with needed params for calculation
/// @param _ltvType acceptable values are only TypeofLTV.MaximumLTV or TypeofLTV.LiquidationThreshold
/// @return limit theoretical LTV limit of `_ltvType`
function calculateLTVLimit(SolvencyParams memory _params, TypeofLTV _ltvType)
internal
view
returns (uint256 limit)
{
IPriceProvidersRepository priceProvidersRepository = _params.siloRepository.priceProvidersRepository();
uint256[] memory collateralValues = getUserCollateralValues(priceProvidersRepository, _params);
// value of user's collateral
uint256 collateralTotalValue = EasyMath.sum(collateralValues);
if (collateralTotalValue == 0) return 0;
// value of theoretical debt user can have depending on TypeofLTV
uint256 borrowAvailableTotalValue = _getTotalAvailableToBorrowValue(
_params.siloRepository,
address(_params.silo),
_params.assets,
_ltvType,
collateralValues
);
limit = borrowAvailableTotalValue * _PRECISION_DECIMALS / collateralTotalValue;
}
/// @notice Returns worth (in quote token) of each collateral deposit of a user
/// @param _priceProvidersRepository address of IPriceProvidersRepository where prices are read
/// @param _params `SolvencyV2.SolvencyParams` struct with needed params for calculation
/// @return collateralValues worth of each collateral deposit of a user as an array
function getUserCollateralValues(IPriceProvidersRepository _priceProvidersRepository, SolvencyParams memory _params)
internal
view
returns(uint256[] memory collateralValues)
{
uint256[] memory collateralAmounts = getCollateralAmounts(_params);
collateralValues = convertAmountsToValues(_priceProvidersRepository, _params.assets, collateralAmounts);
}
/// @notice Convert assets amounts to values in quote token (amount * price)
/// @param _priceProviderRepo address of IPriceProvidersRepository where prices are read
/// @param _assets array with assets for which prices are read
/// @param _amounts array of amounts
/// @return values array of values for corresponding assets
function convertAmountsToValues(
IPriceProvidersRepository _priceProviderRepo,
address[] memory _assets,
uint256[] memory _amounts
) internal view returns (uint256[] memory values) {
if (_assets.length != _amounts.length) revert DifferentArrayLength();
values = new uint256[](_assets.length);
for (uint256 i = 0; i < _assets.length; i++) {
if (_amounts[i] == 0) continue;
uint256 assetPrice = _priceProviderRepo.getPrice(_assets[i]);
uint8 assetDecimals = ERC20(_assets[i]).decimals();
values[i] = _amounts[i].toValue(assetPrice, assetDecimals);
}
}
/// @notice Get amount of collateral for each asset
/// @param _params `SolvencyV2.SolvencyParams` struct with needed params for calculation
/// @return collateralAmounts array of amounts for each token in Silo. May contain zero values if user
/// did not deposit given collateral token.
function getCollateralAmounts(SolvencyParams memory _params)
internal
view
returns (uint256[] memory collateralAmounts)
{
if (_params.assets.length != _params.assetStates.length) {
revert DifferentArrayLength();
}
collateralAmounts = new uint256[](_params.assets.length);
for (uint256 i = 0; i < _params.assets.length; i++) {
uint256 userCollateralTokenBalance = _params.assetStates[i].collateralToken.balanceOf(_params.user);
uint256 userCollateralOnlyTokenBalance = _params.assetStates[i].collateralOnlyToken.balanceOf(_params.user);
if (userCollateralTokenBalance + userCollateralOnlyTokenBalance == 0) continue;
uint256 rcomp = getRcomp(_params.silo, _params.siloRepository, _params.assets[i], block.timestamp);
collateralAmounts[i] = getUserCollateralAmount(
_params.assetStates[i],
userCollateralTokenBalance,
userCollateralOnlyTokenBalance,
rcomp,
_params.siloRepository
);
}
}
/// @notice Get amount of debt for each asset
/// @param _params `SolvencyV2.SolvencyParams` struct with needed params for calculation
/// @return totalBorrowAmounts array of amounts for each token in Silo. May contain zero values if user
/// did not borrow given token.
function getBorrowAmounts(SolvencyParams memory _params)
internal
view
returns (uint256[] memory totalBorrowAmounts)
{
if (_params.assets.length != _params.assetStates.length) {
revert DifferentArrayLength();
}
totalBorrowAmounts = new uint256[](_params.assets.length);
for (uint256 i = 0; i < _params.assets.length; i++) {
uint256 rcomp = getRcomp(_params.silo, _params.siloRepository, _params.assets[i], block.timestamp);
totalBorrowAmounts[i] = getUserBorrowAmount(_params.assetStates[i], _params.user, rcomp);
}
}
/// @notice Get amount of deposited token, including collateralOnly deposits
/// @param _assetStates state of deposited asset in Silo
/// @param _userCollateralTokenBalance balance of user's share collateral token
/// @param _userCollateralOnlyTokenBalance balance of user's share collateralOnly token
/// @param _rcomp compounded interest rate to account for during calculations, could be 0
/// @param _siloRepository SiloRepository address
/// @return amount of underlying token deposited, including collateralOnly deposit
function getUserCollateralAmount(
ISilo.AssetStorage memory _assetStates,
uint256 _userCollateralTokenBalance,
uint256 _userCollateralOnlyTokenBalance,
uint256 _rcomp,
ISiloRepository _siloRepository
) internal view returns (uint256) {
uint256 assetAmount = _userCollateralTokenBalance == 0 ? 0 : _userCollateralTokenBalance.toAmount(
totalDepositsWithInterest(
_assetStates.totalDeposits,
_assetStates.totalBorrowAmount,
_siloRepository.protocolShareFee(),
_rcomp
),
_assetStates.collateralToken.totalSupply()
);
uint256 assetCollateralOnlyAmount = _userCollateralOnlyTokenBalance == 0
? 0
: _userCollateralOnlyTokenBalance.toAmount(
_assetStates.collateralOnlyDeposits,
_assetStates.collateralOnlyToken.totalSupply()
);
return assetAmount + assetCollateralOnlyAmount;
}
/// @notice Get amount of borrowed token
/// @param _assetStates state of borrowed asset in Silo
/// @param _user user wallet address for which to read debt
/// @param _rcomp compounded interest rate to account for during calculations, could be 0
/// @return amount of borrowed token
function getUserBorrowAmount(ISilo.AssetStorage memory _assetStates, address _user, uint256 _rcomp)
internal
view
returns (uint256)
{
uint256 balance = _assetStates.debtToken.balanceOf(_user);
if (balance == 0) return 0;
uint256 totalBorrowAmountCached = totalBorrowAmountWithInterest(_assetStates.totalBorrowAmount, _rcomp);
return balance.toAmountRoundUp(totalBorrowAmountCached, _assetStates.debtToken.totalSupply());
}
/// @notice Get compounded interest rate from the model
/// @param _silo Silo address
/// @param _siloRepository SiloRepository address
/// @param _asset address of asset for which to read interest rate
/// @param _timestamp used to determine amount of time from last rate update
/// @return rcomp compounded interest rate for an asset
function getRcomp(ISilo _silo, ISiloRepository _siloRepository, address _asset, uint256 _timestamp)
internal
view
returns (uint256 rcomp)
{
IInterestRateModel model = _siloRepository.getInterestRateModel(address(_silo), _asset);
rcomp = model.getCompoundInterestRate(address(_silo), _asset, _timestamp);
}
/// @notice Returns total deposits with interest dynamically calculated with the provided rComp
/// @param _assetTotalDeposits total deposits for asset
/// @param _assetTotalBorrows total borrows for asset
/// @param _protocolShareFee `siloRepository.protocolShareFee()`
/// @param _rcomp compounded interest rate
/// @return _totalDepositsWithInterests total deposits amount with interest
function totalDepositsWithInterest(
uint256 _assetTotalDeposits,
uint256 _assetTotalBorrows,
uint256 _protocolShareFee,
uint256 _rcomp
)
internal
pure
returns (uint256 _totalDepositsWithInterests)
{
uint256 depositorsShare = _PRECISION_DECIMALS - _protocolShareFee;
return _assetTotalDeposits + _assetTotalBorrows * _rcomp / _PRECISION_DECIMALS * depositorsShare /
_PRECISION_DECIMALS;
}
/// @notice Returns total borrow amount with interest dynamically calculated with the provided rComp
/// @param _totalBorrowAmount total borrow amount
/// @param _rcomp compounded interest rate
/// @return totalBorrowAmountWithInterests total borrow amount with interest
function totalBorrowAmountWithInterest(uint256 _totalBorrowAmount, uint256 _rcomp)
internal
pure
returns (uint256 totalBorrowAmountWithInterests)
{
totalBorrowAmountWithInterests = _totalBorrowAmount + _totalBorrowAmount * _rcomp / _PRECISION_DECIMALS;
}
/// @notice Calculates protocol liquidation fee and new protocol total fees collected
/// @param _protocolEarnedFees amount of total collected fees so far
/// @param _amount amount on which we will apply fee
/// @param _liquidationFee liquidation fee in SolvencyV2._PRECISION_DECIMALS
/// @return liquidationFeeAmount calculated interest
/// @return newProtocolEarnedFees the new total amount of protocol fees
function calculateLiquidationFee(uint256 _protocolEarnedFees, uint256 _amount, uint256 _liquidationFee)
internal
pure
returns (uint256 liquidationFeeAmount, uint256 newProtocolEarnedFees)
{
unchecked {
// If we overflow on multiplication it should not revert tx, we will get lower fees
liquidationFeeAmount = _amount * _liquidationFee / SolvencyV2._PRECISION_DECIMALS;
if (_protocolEarnedFees > type(uint256).max - liquidationFeeAmount) {
newProtocolEarnedFees = type(uint256).max;
liquidationFeeAmount = type(uint256).max - _protocolEarnedFees;
} else {
newProtocolEarnedFees = _protocolEarnedFees + liquidationFeeAmount;
}
}
}
/// @notice Calculates theoretical value (in quote token) that user could borrow based given collateral value
/// @param _siloRepository SiloRepository address
/// @param _silo Silo address
/// @param _asset address of collateral token
/// @param _type type of LTV limit to use for calculations
/// @param _collateralValue value of collateral deposit (in quote token)
/// @return availableToBorrow value (in quote token) that user can borrow against collateral value
function _getAvailableToBorrowValue(
ISiloRepository _siloRepository,
address _silo,
address _asset,
TypeofLTV _type,
uint256 _collateralValue
) private view returns (uint256 availableToBorrow) {
uint256 assetLTV;
if (_type == TypeofLTV.MaximumLTV) {
assetLTV = _siloRepository.getMaximumLTV(_silo, _asset);
} else if (_type == TypeofLTV.LiquidationThreshold) {
assetLTV = _siloRepository.getLiquidationThreshold(_silo, _asset);
} else {
revert UnsupportedLTVType();
}
// value that can be borrowed against the deposit
// ie. for assetLTV = 50%, 1 ETH * 50% = 0.5 ETH of available to borrow
availableToBorrow = _collateralValue * assetLTV / _PRECISION_DECIMALS;
}
/// @notice Calculates theoretical value (in quote token) that user can borrow based on deposited collateral
/// @param _siloRepository SiloRepository address
/// @param _silo Silo address
/// @param _assets array with assets
/// @param _ltvType type of LTV limit to use for calculations
/// acceptable values are only TypeofLTV.MaximumLTV or TypeofLTV.LiquidationThreshold
/// @param _collateralValues value (worth in quote token) of each deposit made by user
/// @return totalAvailableToBorrowValue value (in quote token) that user can borrow against collaterals
function _getTotalAvailableToBorrowValue(
ISiloRepository _siloRepository,
address _silo,
address[] memory _assets,
TypeofLTV _ltvType,
uint256[] memory _collateralValues
) private view returns (uint256 totalAvailableToBorrowValue) {
if (_assets.length != _collateralValues.length) revert DifferentArrayLength();
for (uint256 i = 0; i < _assets.length; i++) {
totalAvailableToBorrowValue += _getAvailableToBorrowValue(
_siloRepository,
_silo,
_assets[i],
_ltvType,
_collateralValues[i]
);
}
}
}
RevertBytes.sol 14 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6 <=0.9.0;
library RevertBytes {
function revertBytes(bytes memory _errMsg, string memory _customErr) internal pure {
if (_errMsg.length > 0) {
assembly { // solhint-disable-line no-inline-assembly
revert(add(32, _errMsg), mload(_errMsg))
}
}
revert(_customErr);
}
}
ISilo.sol 117 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
import "./IBaseSilo.sol";
interface ISilo is IBaseSilo {
/// @notice Deposit `_amount` of `_asset` tokens from `msg.sender` to the Silo
/// @param _asset The address of the token to deposit
/// @param _amount The amount of the token to deposit
/// @param _collateralOnly True if depositing collateral only
/// @return collateralAmount deposited amount
/// @return collateralShare user collateral shares based on deposited amount
function deposit(address _asset, uint256 _amount, bool _collateralOnly)
external
returns (uint256 collateralAmount, uint256 collateralShare);
/// @notice Router function to deposit `_amount` of `_asset` tokens to the Silo for the `_depositor`
/// @param _asset The address of the token to deposit
/// @param _depositor The address of the recipient of collateral tokens
/// @param _amount The amount of the token to deposit
/// @param _collateralOnly True if depositing collateral only
/// @return collateralAmount deposited amount
/// @return collateralShare `_depositor` collateral shares based on deposited amount
function depositFor(address _asset, address _depositor, uint256 _amount, bool _collateralOnly)
external
returns (uint256 collateralAmount, uint256 collateralShare);
/// @notice Withdraw `_amount` of `_asset` tokens from the Silo to `msg.sender`
/// @param _asset The address of the token to withdraw
/// @param _amount The amount of the token to withdraw
/// @param _collateralOnly True if withdrawing collateral only deposit
/// @return withdrawnAmount withdrawn amount that was transferred to user
/// @return withdrawnShare burned share based on `withdrawnAmount`
function withdraw(address _asset, uint256 _amount, bool _collateralOnly)
external
returns (uint256 withdrawnAmount, uint256 withdrawnShare);
/// @notice Router function to withdraw `_amount` of `_asset` tokens from the Silo for the `_depositor`
/// @param _asset The address of the token to withdraw
/// @param _depositor The address that originally deposited the collateral tokens being withdrawn,
/// it should be the one initiating the withdrawal through the router
/// @param _receiver The address that will receive the withdrawn tokens
/// @param _amount The amount of the token to withdraw
/// @param _collateralOnly True if withdrawing collateral only deposit
/// @return withdrawnAmount withdrawn amount that was transferred to `_receiver`
/// @return withdrawnShare burned share based on `withdrawnAmount`
function withdrawFor(
address _asset,
address _depositor,
address _receiver,
uint256 _amount,
bool _collateralOnly
) external returns (uint256 withdrawnAmount, uint256 withdrawnShare);
/// @notice Borrow `_amount` of `_asset` tokens from the Silo to `msg.sender`
/// @param _asset The address of the token to borrow
/// @param _amount The amount of the token to borrow
/// @return debtAmount borrowed amount
/// @return debtShare user debt share based on borrowed amount
function borrow(address _asset, uint256 _amount) external returns (uint256 debtAmount, uint256 debtShare);
/// @notice Router function to borrow `_amount` of `_asset` tokens from the Silo for the `_receiver`
/// @param _asset The address of the token to borrow
/// @param _borrower The address that will take the loan,
/// it should be the one initiating the borrowing through the router
/// @param _receiver The address of the asset receiver
/// @param _amount The amount of the token to borrow
/// @return debtAmount borrowed amount
/// @return debtShare `_receiver` debt share based on borrowed amount
function borrowFor(address _asset, address _borrower, address _receiver, uint256 _amount)
external
returns (uint256 debtAmount, uint256 debtShare);
/// @notice Repay `_amount` of `_asset` tokens from `msg.sender` to the Silo
/// @param _asset The address of the token to repay
/// @param _amount amount of asset to repay, includes interests
/// @return repaidAmount amount repaid
/// @return burnedShare burned debt share
function repay(address _asset, uint256 _amount) external returns (uint256 repaidAmount, uint256 burnedShare);
/// @notice Allows to repay in behalf of borrower to execute liquidation
/// @param _asset The address of the token to repay
/// @param _borrower The address of the user to have debt tokens burned
/// @param _amount amount of asset to repay, includes interests
/// @return repaidAmount amount repaid
/// @return burnedShare burned debt share
function repayFor(address _asset, address _borrower, uint256 _amount)
external
returns (uint256 repaidAmount, uint256 burnedShare);
/// @dev harvest protocol fees from an array of assets
/// @return harvestedAmounts amount harvested during tx execution for each of silo asset
function harvestProtocolFees() external returns (uint256[] memory harvestedAmounts);
/// @notice Function to update interests for `_asset` token since the last saved state
/// @param _asset The address of the token to be updated
/// @return interest accrued interest
function accrueInterest(address _asset) external returns (uint256 interest);
/// @notice this methods does not requires to have tokens in order to liquidate user
/// @dev during liquidation process, msg.sender will be notified once all collateral will be send to him
/// msg.sender needs to be `IFlashLiquidationReceiver`
/// @param _users array of users to liquidate
/// @param _flashReceiverData this data will be forward to msg.sender on notification
/// @return assets array of all processed assets (collateral + debt, including removed)
/// @return receivedCollaterals receivedCollaterals[userId][assetId] => amount
/// amounts of collaterals send to `_flashReceiver`
/// @return shareAmountsToRepaid shareAmountsToRepaid[userId][assetId] => amount
/// required amounts of debt to be repaid
function flashLiquidate(address[] memory _users, bytes memory _flashReceiverData)
external
returns (
address[] memory assets,
uint256[][] memory receivedCollaterals,
uint256[][] memory shareAmountsToRepaid
);
}
ISwapper.sol 27 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9.0;
interface ISwapper {
/// @dev swaps `_amountIn` of `_tokenIn` for `_tokenOut`. It might require approvals.
/// @return amountOut amount of _tokenOut received
function swapAmountIn(
address _tokenIn,
address _tokenOut,
uint256 _amountIn,
address _priceProvider,
address _siloAsset
) external returns (uint256 amountOut);
/// @dev swaps `_tokenIn` for `_amountOut` of `_tokenOut`. It might require approvals
/// @return amountIn amount of _tokenIn spend
function swapAmountOut(
address _tokenIn,
address _tokenOut,
uint256 _amountOut,
address _priceProvider,
address _siloAsset
) external returns (uint256 amountIn);
/// @return address that needs to have approval to spend tokens to execute a swap
function spenderToApprove() external view returns (address);
}
IBaseSilo.sol 172 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
import "./IShareToken.sol";
import "./IFlashLiquidationReceiver.sol";
import "./ISiloRepository.sol";
interface IBaseSilo {
enum AssetStatus { Undefined, Active, Removed }
/// @dev Storage struct that holds all required data for a single token market
struct AssetStorage {
/// @dev Token that represents a share in totalDeposits of Silo
IShareToken collateralToken;
/// @dev Token that represents a share in collateralOnlyDeposits of Silo
IShareToken collateralOnlyToken;
/// @dev Token that represents a share in totalBorrowAmount of Silo
IShareToken debtToken;
/// @dev COLLATERAL: Amount of asset token that has been deposited to Silo with interest earned by depositors.
/// It also includes token amount that has been borrowed.
uint256 totalDeposits;
/// @dev COLLATERAL ONLY: Amount of asset token that has been deposited to Silo that can be ONLY used
/// as collateral. These deposits do NOT earn interest and CANNOT be borrowed.
uint256 collateralOnlyDeposits;
/// @dev DEBT: Amount of asset token that has been borrowed with accrued interest.
uint256 totalBorrowAmount;
}
/// @dev Storage struct that holds data related to fees and interest
struct AssetInterestData {
/// @dev Total amount of already harvested protocol fees
uint256 harvestedProtocolFees;
/// @dev Total amount (ever growing) of asset token that has been earned by the protocol from
/// generated interest.
uint256 protocolFees;
/// @dev Timestamp of the last time `interestRate` has been updated in storage.
uint64 interestRateTimestamp;
/// @dev True if asset was removed from the protocol. If so, deposit and borrow functions are disabled
/// for that asset
AssetStatus status;
}
/// @notice data that InterestModel needs for calculations
struct UtilizationData {
uint256 totalDeposits;
uint256 totalBorrowAmount;
/// @dev timestamp of last interest accrual
uint64 interestRateTimestamp;
}
/// @dev Shares names and symbols that are generated while asset initialization
struct AssetSharesMetadata {
/// @dev Name for the collateral shares token
string collateralName;
/// @dev Symbol for the collateral shares token
string collateralSymbol;
/// @dev Name for the collateral only (protected collateral) shares token
string protectedName;
/// @dev Symbol for the collateral only (protected collateral) shares token
string protectedSymbol;
/// @dev Name for the debt shares token
string debtName;
/// @dev Symbol for the debt shares token
string debtSymbol;
}
/// @notice Emitted when deposit is made
/// @param asset asset address that was deposited
/// @param depositor wallet address that deposited asset
/// @param amount amount of asset that was deposited
/// @param collateralOnly type of deposit, true if collateralOnly deposit was used
event Deposit(address indexed asset, address indexed depositor, uint256 amount, bool collateralOnly);
/// @notice Emitted when withdraw is made
/// @param asset asset address that was withdrawn
/// @param depositor wallet address that deposited asset
/// @param receiver wallet address that received asset
/// @param amount amount of asset that was withdrew
/// @param collateralOnly type of withdraw, true if collateralOnly deposit was used
event Withdraw(
address indexed asset,
address indexed depositor,
address indexed receiver,
uint256 amount,
bool collateralOnly
);
/// @notice Emitted on asset borrow
/// @param asset asset address that was borrowed
/// @param user wallet address that borrowed asset
/// @param amount amount of asset that was borrowed
event Borrow(address indexed asset, address indexed user, uint256 amount);
/// @notice Emitted on asset repay
/// @param asset asset address that was repaid
/// @param user wallet address that repaid asset
/// @param amount amount of asset that was repaid
event Repay(address indexed asset, address indexed user, uint256 amount);
/// @notice Emitted on user liquidation
/// @param asset asset address that was liquidated
/// @param user wallet address that was liquidated
/// @param shareAmountRepaid amount of collateral-share token that was repaid. This is collateral token representing
/// ownership of underlying deposit.
/// @param seizedCollateral amount of underlying token that was seized by liquidator
event Liquidate(address indexed asset, address indexed user, uint256 shareAmountRepaid, uint256 seizedCollateral);
/// @notice Emitted when the status for an asset is updated
/// @param asset asset address that was updated
/// @param status new asset status
event AssetStatusUpdate(address indexed asset, AssetStatus indexed status);
/// @return version of the silo contract
function VERSION() external returns (uint128); // solhint-disable-line func-name-mixedcase
/// @notice Synchronize current bridge assets with Silo
/// @dev This function needs to be called on Silo deployment to setup all assets for Silo. It needs to be
/// called every time a bridged asset is added or removed. When bridge asset is removed, depositing and borrowing
/// should be disabled during asset sync.
function syncBridgeAssets() external;
/// @notice Get Silo Repository contract address
/// @return Silo Repository contract address
function siloRepository() external view returns (ISiloRepository);
/// @notice Get asset storage data
/// @param _asset asset address
/// @return AssetStorage struct
function assetStorage(address _asset) external view returns (AssetStorage memory);
/// @notice Get asset interest data
/// @param _asset asset address
/// @return AssetInterestData struct
function interestData(address _asset) external view returns (AssetInterestData memory);
/// @dev helper method for InterestRateModel calculations
function utilizationData(address _asset) external view returns (UtilizationData memory data);
/// @notice Calculates solvency of an account
/// @param _user wallet address for which solvency is calculated
/// @return true if solvent, false otherwise
function isSolvent(address _user) external view returns (bool);
/// @notice Returns all initialized (synced) assets of Silo including current and removed bridge assets
/// @return assets array of initialized assets of Silo
function getAssets() external view returns (address[] memory assets);
/// @notice Returns all initialized (synced) assets of Silo including current and removed bridge assets
/// with corresponding state
/// @return assets array of initialized assets of Silo
/// @return assetsStorage array of assets state corresponding to `assets` array
function getAssetsWithState() external view returns (address[] memory assets, AssetStorage[] memory assetsStorage);
/// @notice Check if depositing an asset for given account is possible
/// @dev Depositing an asset that has been already borrowed (and vice versa) is disallowed
/// @param _asset asset we want to deposit
/// @param _depositor depositor address
/// @return true if asset can be deposited by depositor
function depositPossible(address _asset, address _depositor) external view returns (bool);
/// @notice Check if borrowing an asset for given account is possible
/// @dev Borrowing an asset that has been already deposited (and vice versa) is disallowed
/// @param _asset asset we want to deposit
/// @param _borrower borrower address
/// @return true if asset can be borrowed by borrower
function borrowPossible(address _asset, address _borrower) external view returns (bool);
/// @dev Amount of token that is available for borrowing
/// @param _asset asset to get liquidity for
/// @return Silo liquidity
function liquidity(address _asset) external view returns (uint256);
}
IShareToken.sol 26 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "./INotificationReceiver.sol";
interface IShareToken is IERC20Metadata {
/// @notice Emitted every time receiver is notified about token transfer
/// @param notificationReceiver receiver address
/// @param success false if TX reverted on `notificationReceiver` side, otherwise true
event NotificationSent(
INotificationReceiver indexed notificationReceiver,
bool success
);
/// @notice Mint method for Silo to create debt position
/// @param _account wallet for which to mint token
/// @param _amount amount of token to be minted
function mint(address _account, uint256 _amount) external;
/// @notice Burn method for Silo to close debt position
/// @param _account wallet for which to burn token
/// @param _amount amount of token to be burned
function burn(address _account, uint256 _amount) external;
}
ZeroExSwap.sol 57 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../lib/RevertBytes.sol";
/// @dev Based on demo contract that swaps its ERC20 balance for another ERC20.
/// demo source: https://github.com/0xProject/0x-api-starter-guide-code/blob/master/contracts/SimpleTokenSwap.sol
contract ZeroExSwap {
using RevertBytes for bytes;
/// @param sellToken The `sellTokenAddress` field from the API response.
/// @param buyToken The `buyTokenAddress` field from the API response.
/// @param allowanceTarget The `allowanceTarget` field from the API response.
/// @param swapCallData The `data` field from the API response.
struct SwapInput0x {
address sellToken;
address allowanceTarget;
bytes swapCallData;
}
/// @dev 0x ExchangeProxy address.
/// See https://docs.0x.org/developer-resources/contract-addresses
/// The `to` field from the API response, but at the same time,
/// TODO: maybe unit test that will check, if it does not changed?
// solhint-disable-next-line var-name-mixedcase
address public immutable EXCHANGE_PROXY;
event BoughtTokens(address sellToken, address buyToken, uint256 boughtAmount);
error AddressZero();
error TargetNotExchangeProxy();
error ApprovalFailed();
constructor(address _exchangeProxy) {
if (_exchangeProxy == address(0)) revert AddressZero();
EXCHANGE_PROXY = _exchangeProxy;
}
/// @dev Swaps ERC20->ERC20 tokens held by this contract using a 0x-API quote.
/// Must attach ETH equal to the `value` field from the API response.
/// @param _sellToken The `sellTokenAddress` field from the API response.
/// @param _spender The `allowanceTarget` field from the API response.
/// @param _swapCallData The `data` field from the API response.
function fillQuote(address _sellToken, address _spender, bytes memory _swapCallData) public {
IERC20(_sellToken).approve(_spender, type(uint256).max);
// Call the encoded swap function call on the contract at `swapTarget`
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory data) = EXCHANGE_PROXY.call(_swapCallData);
if (!success) data.revertBytes("SWAP_CALL_FAILED");
IERC20(_sellToken).approve(_spender, 0);
}
}
ISiloFactory.sol 24 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
interface ISiloFactory {
/// @notice Emitted when Silo is deployed
/// @param silo address of deployed Silo
/// @param asset address of asset for which Silo was deployed
/// @param version version of silo implementation
event NewSiloCreated(address indexed silo, address indexed asset, uint128 version);
/// @notice Must be called by repository on constructor
/// @param _siloRepository the SiloRepository to set
function initRepository(address _siloRepository) external;
/// @notice Deploys Silo
/// @param _siloAsset unique asset for which Silo is deployed
/// @param _version version of silo implementation
/// @param _data (optional) data that may be needed during silo creation
/// @return silo deployed Silo address
function createSilo(address _siloAsset, uint128 _version, bytes memory _data) external returns (address silo);
/// @dev just a helper method to see if address is a factory
function siloFactoryPing() external pure returns (bytes4);
}
IPriceProvider.sol 30 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6 <0.9.0;
/// @title Common interface for Silo Price Providers
interface IPriceProvider {
/// @notice Returns "Time-Weighted Average Price" for an asset. Calculates TWAP price for quote/asset.
/// It unifies all tokens decimal to 18, examples:
/// - if asses == quote it returns 1e18
/// - if asset is USDC and quote is ETH and ETH costs ~$3300 then it returns ~0.0003e18 WETH per 1 USDC
/// @param _asset address of an asset for which to read price
/// @return price of asses with 18 decimals, throws when pool is not ready yet to provide price
function getPrice(address _asset) external view returns (uint256 price);
/// @dev Informs if PriceProvider is setup for asset. It does not means PriceProvider can provide price right away.
/// Some providers implementations need time to "build" buffer for TWAP price,
/// so price may not be available yet but this method will return true.
/// @param _asset asset in question
/// @return TRUE if asset has been setup, otherwise false
function assetSupported(address _asset) external view returns (bool);
/// @notice Gets token address in which prices are quoted
/// @return quoteToken address
function quoteToken() external view returns (address);
/// @notice Helper method that allows easily detects, if contract is PriceProvider
/// @dev this can save us from simple human errors, in case we use invalid address
/// but this should NOT be treated as security check
/// @return always true
function priceProviderPing() external pure returns (bytes4);
}
ITokensFactory.sol 46 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
import "./IShareToken.sol";
interface ITokensFactory {
/// @notice Emitted when collateral token is deployed
/// @param token address of deployed collateral token
event NewShareCollateralTokenCreated(address indexed token);
/// @notice Emitted when collateral token is deployed
/// @param token address of deployed debt token
event NewShareDebtTokenCreated(address indexed token);
///@notice Must be called by repository on constructor
/// @param _siloRepository the SiloRepository to set
function initRepository(address _siloRepository) external;
/// @notice Deploys collateral token
/// @param _name name of the token
/// @param _symbol symbol of the token
/// @param _asset underlying asset for which token is deployed
/// @return address of deployed collateral share token
function createShareCollateralToken(
string memory _name,
string memory _symbol,
address _asset
) external returns (IShareToken);
/// @notice Deploys debt token
/// @param _name name of the token
/// @param _symbol symbol of the token
/// @param _asset underlying asset for which token is deployed
/// @return address of deployed debt share token
function createShareDebtToken(
string memory _name,
string memory _symbol,
address _asset
)
external
returns (IShareToken);
/// @dev just a helper method to see if address is a factory
/// @return always true
function tokensFactoryPing() external pure returns (bytes4);
}
ISiloRepository.sol 341 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
import "./ISiloFactory.sol";
import "./ITokensFactory.sol";
import "./IPriceProvidersRepository.sol";
import "./INotificationReceiver.sol";
import "./IInterestRateModel.sol";
interface ISiloRepository {
/// @dev protocol fees in precision points (Solvency._PRECISION_DECIMALS), we do allow for fee == 0
struct Fees {
/// @dev One time protocol fee for opening a borrow position in precision points (Solvency._PRECISION_DECIMALS)
uint64 entryFee;
/// @dev Protocol revenue share in interest paid in precision points (Solvency._PRECISION_DECIMALS)
uint64 protocolShareFee;
/// @dev Protocol share in liquidation profit in precision points (Solvency._PRECISION_DECIMALS).
/// It's calculated from total collateral amount to be transferred to liquidator.
uint64 protocolLiquidationFee;
}
struct SiloVersion {
/// @dev Default version of Silo. If set to 0, it means it is not set. By default it is set to 1
uint128 byDefault;
/// @dev Latest added version of Silo. If set to 0, it means it is not set. By default it is set to 1
uint128 latest;
}
/// @dev AssetConfig struct represents configurable parameters for each Silo
struct AssetConfig {
/// @dev Loan-to-Value ratio represents the maximum borrowing power of a specific collateral.
/// For example, if the collateral asset has an LTV of 75%, the user can borrow up to 0.75 worth
/// of quote token in the principal currency for every quote token worth of collateral.
/// value uses 18 decimals eg. 100% == 1e18
/// max valid value is 1e18 so it needs storage of 60 bits
uint64 maxLoanToValue;
/// @dev Liquidation Threshold represents the threshold at which a borrow position will be considered
/// undercollateralized and subject to liquidation for each collateral. For example,
/// if a collateral has a liquidation threshold of 80%, it means that the loan will be
/// liquidated when the borrowAmount value is worth 80% of the collateral value.
/// value uses 18 decimals eg. 100% == 1e18
uint64 liquidationThreshold;
/// @dev interest rate model address
IInterestRateModel interestRateModel;
}
event NewDefaultMaximumLTV(uint64 defaultMaximumLTV);
event NewDefaultLiquidationThreshold(uint64 defaultLiquidationThreshold);
/// @notice Emitted on new Silo creation
/// @param silo deployed Silo address
/// @param asset unique asset for deployed Silo
/// @param siloVersion version of deployed Silo
event NewSilo(address indexed silo, address indexed asset, uint128 siloVersion);
/// @notice Emitted when new Silo (or existing one) becomes a bridge pool (pool with only bridge tokens).
/// @param pool address of the bridge pool, It can be zero address when bridge asset is removed and pool no longer
/// is treated as bridge pool
event BridgePool(address indexed pool);
/// @notice Emitted on new bridge asset
/// @param newBridgeAsset address of added bridge asset
event BridgeAssetAdded(address indexed newBridgeAsset);
/// @notice Emitted on removed bridge asset
/// @param bridgeAssetRemoved address of removed bridge asset
event BridgeAssetRemoved(address indexed bridgeAssetRemoved);
/// @notice Emitted when default interest rate model is changed
/// @param newModel address of new interest rate model
event InterestRateModel(IInterestRateModel indexed newModel);
/// @notice Emitted on price provider repository address update
/// @param newProvider address of new oracle repository
event PriceProvidersRepositoryUpdate(
IPriceProvidersRepository indexed newProvider
);
/// @notice Emitted on token factory address update
/// @param newTokensFactory address of new token factory
event TokensFactoryUpdate(address indexed newTokensFactory);
/// @notice Emitted on router address update
/// @param newRouter address of new router
event RouterUpdate(address indexed newRouter);
/// @notice Emitted on INotificationReceiver address update
/// @param newIncentiveContract address of new INotificationReceiver
event NotificationReceiverUpdate(INotificationReceiver indexed newIncentiveContract);
/// @notice Emitted when new Silo version is registered
/// @param factory factory address that deploys registered Silo version
/// @param siloLatestVersion Silo version of registered Silo
/// @param siloDefaultVersion current default Silo version
event RegisterSiloVersion(address indexed factory, uint128 siloLatestVersion, uint128 siloDefaultVersion);
/// @notice Emitted when Silo version is unregistered
/// @param factory factory address that deploys unregistered Silo version
/// @param siloVersion version that was unregistered
event UnregisterSiloVersion(address indexed factory, uint128 siloVersion);
/// @notice Emitted when default Silo version is updated
/// @param newDefaultVersion new default version
event SiloDefaultVersion(uint128 newDefaultVersion);
/// @notice Emitted when default fee is updated
/// @param newEntryFee new entry fee
/// @param newProtocolShareFee new protocol share fee
/// @param newProtocolLiquidationFee new protocol liquidation fee
event FeeUpdate(
uint64 newEntryFee,
uint64 newProtocolShareFee,
uint64 newProtocolLiquidationFee
);
/// @notice Emitted when asset config is updated for a silo
/// @param silo silo for which asset config is being set
/// @param asset asset for which asset config is being set
/// @param assetConfig new asset config
event AssetConfigUpdate(address indexed silo, address indexed asset, AssetConfig assetConfig);
/// @notice Emitted when silo (silo factory) version is set for asset
/// @param asset asset for which asset config is being set
/// @param version Silo version
event VersionForAsset(address indexed asset, uint128 version);
/// @param _siloAsset silo asset
/// @return version of Silo that is assigned for provided asset, if not assigned it returns zero (default)
function getVersionForAsset(address _siloAsset) external returns (uint128);
/// @notice setter for `getVersionForAsset` mapping
/// @param _siloAsset silo asset
/// @param _version version of Silo that will be assigned for `_siloAsset`, zero (default) is acceptable
function setVersionForAsset(address _siloAsset, uint128 _version) external;
/// @notice use this method only when off-chain verification is OFF
/// @dev Silo does NOT support rebase and deflationary tokens
/// @param _siloAsset silo asset
/// @param _siloData (optional) data that may be needed during silo creation
/// @return createdSilo address of created silo
function newSilo(address _siloAsset, bytes memory _siloData) external returns (address createdSilo);
/// @notice use this method to deploy new version of Silo for an asset that already has Silo deployed.
/// Only owner (DAO) can replace.
/// @dev Silo does NOT support rebase and deflationary tokens
/// @param _siloAsset silo asset
/// @param _siloVersion version of silo implementation. Use 0 for default version which is fine
/// for 99% of cases.
/// @param _siloData (optional) data that may be needed during silo creation
/// @return createdSilo address of created silo
function replaceSilo(
address _siloAsset,
uint128 _siloVersion,
bytes memory _siloData
) external returns (address createdSilo);
/// @notice Set factory contract for debt and collateral tokens for each Silo asset
/// @dev Callable only by owner
/// @param _tokensFactory address of TokensFactory contract that deploys debt and collateral tokens
function setTokensFactory(address _tokensFactory) external;
/// @notice Set default fees
/// @dev Callable only by owner
/// @param _fees:
/// - _entryFee one time protocol fee for opening a borrow position in precision points
/// (Solvency._PRECISION_DECIMALS)
/// - _protocolShareFee protocol revenue share in interest paid in precision points
/// (Solvency._PRECISION_DECIMALS)
/// - _protocolLiquidationFee protocol share in liquidation profit in precision points
/// (Solvency._PRECISION_DECIMALS). It's calculated from total collateral amount to be transferred
/// to liquidator.
function setFees(Fees calldata _fees) external;
/// @notice Set configuration for given asset in given Silo
/// @dev Callable only by owner
/// @param _silo Silo address for which config applies
/// @param _asset asset address for which config applies
/// @param _assetConfig:
/// - _maxLoanToValue maximum Loan-to-Value, for details see `Repository.AssetConfig.maxLoanToValue`
/// - _liquidationThreshold liquidation threshold, for details see `Repository.AssetConfig.maxLoanToValue`
/// - _interestRateModel interest rate model address, for details see `Repository.AssetConfig.interestRateModel`
function setAssetConfig(
address _silo,
address _asset,
AssetConfig calldata _assetConfig
) external;
/// @notice Set default interest rate model
/// @dev Callable only by owner
/// @param _defaultInterestRateModel default interest rate model
function setDefaultInterestRateModel(IInterestRateModel _defaultInterestRateModel) external;
/// @notice Set default maximum LTV
/// @dev Callable only by owner
/// @param _defaultMaxLTV default maximum LTV in precision points (Solvency._PRECISION_DECIMALS)
function setDefaultMaximumLTV(uint64 _defaultMaxLTV) external;
/// @notice Set default liquidation threshold
/// @dev Callable only by owner
/// @param _defaultLiquidationThreshold default liquidation threshold in precision points
/// (Solvency._PRECISION_DECIMALS)
function setDefaultLiquidationThreshold(uint64 _defaultLiquidationThreshold) external;
/// @notice Set price provider repository
/// @dev Callable only by owner
/// @param _repository price provider repository address
function setPriceProvidersRepository(IPriceProvidersRepository _repository) external;
/// @notice Set router contract
/// @dev Callable only by owner
/// @param _router router address
function setRouter(address _router) external;
/// @notice Set NotificationReceiver contract
/// @dev Callable only by owner
/// @param _silo silo address for which to set `_notificationReceiver`
/// @param _notificationReceiver NotificationReceiver address
function setNotificationReceiver(address _silo, INotificationReceiver _notificationReceiver) external;
/// @notice Adds new bridge asset
/// @dev New bridge asset must be unique. Duplicates in bridge assets are not allowed. It's possible to add
/// bridge asset that has been removed in the past. Note that all Silos must be synced manually. Callable
/// only by owner.
/// @param _newBridgeAsset bridge asset address
function addBridgeAsset(address _newBridgeAsset) external;
/// @notice Removes bridge asset
/// @dev Note that all Silos must be synced manually. Callable only by owner.
/// @param _bridgeAssetToRemove bridge asset address to be removed
function removeBridgeAsset(address _bridgeAssetToRemove) external;
/// @notice Registers new Silo version
/// @dev User can choose which Silo version he wants to deploy. It's possible to have multiple versions of Silo.
/// Callable only by owner.
/// @param _factory factory contract that deploys new version of Silo
/// @param _isDefault true if this version should be used as default
function registerSiloVersion(ISiloFactory _factory, bool _isDefault) external;
/// @notice Unregisters Silo version
/// @dev Callable only by owner.
/// @param _siloVersion Silo version to be unregistered
function unregisterSiloVersion(uint128 _siloVersion) external;
/// @notice Sets default Silo version
/// @dev Callable only by owner.
/// @param _defaultVersion Silo version to be set as default
function setDefaultSiloVersion(uint128 _defaultVersion) external;
/// @notice Check if contract address is a Silo deployment
/// @param _silo address of expected Silo
/// @return true if address is Silo deployment, otherwise false
function isSilo(address _silo) external view returns (bool);
/// @notice Get Silo address of asset
/// @param _asset address of asset
/// @return address of corresponding Silo deployment
function getSilo(address _asset) external view returns (address);
/// @notice Get Silo Factory for given version
/// @param _siloVersion version of Silo implementation
/// @return ISiloFactory contract that deploys Silos of given version
function siloFactory(uint256 _siloVersion) external view returns (ISiloFactory);
/// @notice Get debt and collateral Token Factory
/// @return ITokensFactory contract that deploys debt and collateral tokens
function tokensFactory() external view returns (ITokensFactory);
/// @notice Get Router contract
/// @return address of router contract
function router() external view returns (address);
/// @notice Get current bridge assets
/// @dev Keep in mind that not all Silos may be synced with current bridge assets so it's possible that some
/// assets in that list are not part of given Silo.
/// @return address array of bridge assets
function getBridgeAssets() external view returns (address[] memory);
/// @notice Get removed bridge assets
/// @dev Keep in mind that not all Silos may be synced with bridge assets so it's possible that some
/// assets in that list are still part of given Silo.
/// @return address array of bridge assets
function getRemovedBridgeAssets() external view returns (address[] memory);
/// @notice Get maximum LTV for asset in given Silo
/// @dev If dedicated config is not set, method returns default config
/// @param _silo address of Silo
/// @param _asset address of an asset
/// @return maximum LTV in precision points (Solvency._PRECISION_DECIMALS)
function getMaximumLTV(address _silo, address _asset) external view returns (uint256);
/// @notice Get Interest Rate Model address for asset in given Silo
/// @dev If dedicated config is not set, method returns default config
/// @param _silo address of Silo
/// @param _asset address of an asset
/// @return address of interest rate model
function getInterestRateModel(address _silo, address _asset) external view returns (IInterestRateModel);
/// @notice Get liquidation threshold for asset in given Silo
/// @dev If dedicated config is not set, method returns default config
/// @param _silo address of Silo
/// @param _asset address of an asset
/// @return liquidation threshold in precision points (Solvency._PRECISION_DECIMALS)
function getLiquidationThreshold(address _silo, address _asset) external view returns (uint256);
/// @notice Get incentive contract address. Incentive contracts are responsible for distributing rewards
/// to debt and/or collateral token holders of given Silo
/// @param _silo address of Silo
/// @return incentive contract address
function getNotificationReceiver(address _silo) external view returns (INotificationReceiver);
/// @notice Get owner role address of Repository
/// @return owner role address
function owner() external view returns (address);
/// @notice get PriceProvidersRepository contract that manages price providers implementations
/// @return IPriceProvidersRepository address
function priceProvidersRepository() external view returns (IPriceProvidersRepository);
/// @dev Get protocol fee for opening a borrow position
/// @return fee in precision points (Solvency._PRECISION_DECIMALS == 100%)
function entryFee() external view returns (uint256);
/// @dev Get protocol share fee
/// @return protocol share fee in precision points (Solvency._PRECISION_DECIMALS == 100%)
function protocolShareFee() external view returns (uint256);
/// @dev Get protocol liquidation fee
/// @return protocol liquidation fee in precision points (Solvency._PRECISION_DECIMALS == 100%)
function protocolLiquidationFee() external view returns (uint256);
/// @dev Checks all conditions for new silo creation and throws when not possible to create
/// @param _asset address of asset for which you want to create silo
/// @param _assetIsABridge bool TRUE when `_asset` is bridge asset, FALSE when it is not
function ensureCanCreateSiloFor(address _asset, bool _assetIsABridge) external view;
function siloRepositoryPing() external pure returns (bytes4);
}
Address.sol 217 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)
pragma solidity ^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;
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");
(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");
(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");
(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");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal 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
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
Context.sol 24 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
IPriceProviderV2.sol 15 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6 <0.9.0;
import "./IPriceProvider.sol";
/// @title Common interface V2 for Silo Price Providers
interface IPriceProviderV2 is IPriceProvider {
/// @dev for liquidation purposes and for compatibility with naming convention we already using in LiquidationHelper
/// we have this method to return on-chain provider that can be useful for liquidation
function getFallbackProvider(address _asset) external view returns (IPriceProvider);
/// @dev this is info method for LiquidationHelper
/// @return bool TRUE if provider is off-chain, means it is not a dex
function offChainProvider() external pure returns (bool);
}
IERC20LikeV2.sol 10 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6;
/// @dev This is only meant to be used by price providers, which use a different
/// Solidity version than the rest of the codebase. This way de won't need to include
/// an additional version of OpenZeppelin's library.
interface IERC20LikeV2 {
function decimals() external view returns (uint8);
function balanceOf(address) external view returns(uint256);
}
Ownable.sol 76 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^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() {
_transferOwnership(_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 {
_transferOwnership(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");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
LiquidationRepay.sol 51 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../interfaces/ISilo.sol";
/// @notice LiquidationHelper IS NOT PART OF THE PROTOCOL. SILO CREATED THIS TOOL, MOSTLY AS AN EXAMPLE.
/// see https://github.com/silo-finance/liquidation#readme for details how liquidation process should look like
abstract contract LiquidationRepay {
error RepayFailed();
error RepayApprovalFailed();
function _repay(
ISilo _silo,
address _user,
address[] calldata _assets,
uint256[] calldata _shareAmountsToRepaid
) internal virtual {
for (uint256 i = 0; i < _assets.length;) {
if (_shareAmountsToRepaid[i] != 0) {
_repayAsset(_silo, _user, _assets[i], _shareAmountsToRepaid[i]);
}
// we will never have that many assets to overflow
unchecked { i++; }
}
}
function _repayAsset(
ISilo _silo,
address _user,
address _asset,
uint256 _shareAmountToRepaid
) internal virtual {
// Low level call needed to support non-standard `ERC20.approve` eg like `USDT.approve`
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = _asset.call(abi.encodeCall(IERC20.approve, (address(_silo), _shareAmountToRepaid)));
if (!success) revert RepayApprovalFailed();
_silo.repayFor(_asset, _user, _shareAmountToRepaid);
// DEFLATIONARY TOKENS ARE NOT SUPPORTED
// we are not using lower limits for swaps so we may not get enough tokens to do full repay
// our assumption here is that `_shareAmountsToRepaid[i]` is total amount to repay the full debt
// if after repay user has no debt in this asset, the swap is acceptable
if (_silo.assetStorage(_asset).debtToken.balanceOf(_user) != 0) {
revert RepayFailed();
}
}
}
PriceProvider.sol 51 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6 <0.9.0;
import "../lib/Ping.sol";
import "../interfaces/IPriceProvider.sol";
import "../interfaces/IPriceProvidersRepository.sol";
/// @title PriceProvider
/// @notice Abstract PriceProvider contract, parent of all PriceProviders
/// @dev Price provider is a contract that directly integrates with a price source, ie. a DEX or alternative system
/// like Chainlink to calculate TWAP prices for assets. Each price provider should support a single price source
/// and multiple assets.
abstract contract PriceProvider is IPriceProvider {
/// @notice PriceProvidersRepository address
IPriceProvidersRepository public immutable priceProvidersRepository;
/// @notice Token address which prices are quoted in. Must be the same as PriceProvidersRepository.quoteToken
address public immutable override quoteToken;
modifier onlyManager() {
if (priceProvidersRepository.manager() != msg.sender) revert("OnlyManager");
_;
}
/// @param _priceProvidersRepository address of PriceProvidersRepository
constructor(IPriceProvidersRepository _priceProvidersRepository) {
if (
!Ping.pong(_priceProvidersRepository.priceProvidersRepositoryPing)
) {
revert("InvalidPriceProviderRepository");
}
priceProvidersRepository = _priceProvidersRepository;
quoteToken = _priceProvidersRepository.quoteToken();
}
/// @inheritdoc IPriceProvider
function priceProviderPing() external pure override returns (bytes4) {
return this.priceProviderPing.selector;
}
function _revertBytes(bytes memory _errMsg, string memory _customErr) internal pure {
if (_errMsg.length > 0) {
assembly { // solhint-disable-line no-inline-assembly
revert(add(32, _errMsg), mload(_errMsg))
}
}
revert(_customErr);
}
}
IInterestRateModel.sol 148 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
interface IInterestRateModel {
/* solhint-disable */
struct Config {
// uopt ∈ (0, 1) – optimal utilization;
int256 uopt;
// ucrit ∈ (uopt, 1) – threshold of large utilization;
int256 ucrit;
// ulow ∈ (0, uopt) – threshold of low utilization
int256 ulow;
// ki > 0 – integrator gain
int256 ki;
// kcrit > 0 – proportional gain for large utilization
int256 kcrit;
// klow ≥ 0 – proportional gain for low utilization
int256 klow;
// klin ≥ 0 – coefficient of the lower linear bound
int256 klin;
// beta ≥ 0 - a scaling factor
int256 beta;
// ri ≥ 0 – initial value of the integrator
int256 ri;
// Tcrit ≥ 0 - the time during which the utilization exceeds the critical value
int256 Tcrit;
}
/* solhint-enable */
/// @dev Set dedicated config for given asset in a Silo. Config is per asset per Silo so different assets
/// in different Silo can have different configs.
/// It will try to call `_silo.accrueInterest(_asset)` before updating config, but it is not guaranteed,
/// that this call will be successful, if it fail config will be set anyway.
/// @param _silo Silo address for which config should be set
/// @param _asset asset address for which config should be set
function setConfig(address _silo, address _asset, Config calldata _config) external;
/// @dev get compound interest rate and update model storage
/// @param _asset address of an asset in Silo for which interest rate should be calculated
/// @param _blockTimestamp current block timestamp
/// @return rcomp compounded interest rate from last update until now (1e18 == 100%)
function getCompoundInterestRateAndUpdate(
address _asset,
uint256 _blockTimestamp
) external returns (uint256 rcomp);
/// @dev Get config for given asset in a Silo. If dedicated config is not set, default one will be returned.
/// @param _silo Silo address for which config should be set
/// @param _asset asset address for which config should be set
/// @return Config struct for asset in Silo
function getConfig(address _silo, address _asset) external view returns (Config memory);
/// @dev get compound interest rate
/// @param _silo address of Silo
/// @param _asset address of an asset in Silo for which interest rate should be calculated
/// @param _blockTimestamp current block timestamp
/// @return rcomp compounded interest rate from last update until now (1e18 == 100%)
function getCompoundInterestRate(
address _silo,
address _asset,
uint256 _blockTimestamp
) external view returns (uint256 rcomp);
/// @dev get current annual interest rate
/// @param _silo address of Silo
/// @param _asset address of an asset in Silo for which interest rate should be calculated
/// @param _blockTimestamp current block timestamp
/// @return rcur current annual interest rate (1e18 == 100%)
function getCurrentInterestRate(
address _silo,
address _asset,
uint256 _blockTimestamp
) external view returns (uint256 rcur);
/// @notice get the flag to detect rcomp restriction (zero current interest) due to overflow
/// overflow boolean flag to detect rcomp restriction
function overflowDetected(
address _silo,
address _asset,
uint256 _blockTimestamp
) external view returns (bool overflow);
/// @dev pure function that calculates current annual interest rate
/// @param _c configuration object, InterestRateModel.Config
/// @param _totalBorrowAmount current total borrows for asset
/// @param _totalDeposits current total deposits for asset
/// @param _interestRateTimestamp timestamp of last interest rate update
/// @param _blockTimestamp current block timestamp
/// @return rcur current annual interest rate (1e18 == 100%)
function calculateCurrentInterestRate(
Config memory _c,
uint256 _totalDeposits,
uint256 _totalBorrowAmount,
uint256 _interestRateTimestamp,
uint256 _blockTimestamp
) external pure returns (uint256 rcur);
/// @dev pure function that calculates interest rate based on raw input data
/// @param _c configuration object, InterestRateModel.Config
/// @param _totalBorrowAmount current total borrows for asset
/// @param _totalDeposits current total deposits for asset
/// @param _interestRateTimestamp timestamp of last interest rate update
/// @param _blockTimestamp current block timestamp
/// @return rcomp compounded interest rate from last update until now (1e18 == 100%)
/// @return ri current integral part of the rate
/// @return Tcrit time during which the utilization exceeds the critical value
/// @return overflow boolean flag to detect rcomp restriction
function calculateCompoundInterestRateWithOverflowDetection(
Config memory _c,
uint256 _totalDeposits,
uint256 _totalBorrowAmount,
uint256 _interestRateTimestamp,
uint256 _blockTimestamp
) external pure returns (
uint256 rcomp,
int256 ri,
int256 Tcrit, // solhint-disable-line var-name-mixedcase
bool overflow
);
/// @dev pure function that calculates interest rate based on raw input data
/// @param _c configuration object, InterestRateModel.Config
/// @param _totalBorrowAmount current total borrows for asset
/// @param _totalDeposits current total deposits for asset
/// @param _interestRateTimestamp timestamp of last interest rate update
/// @param _blockTimestamp current block timestamp
/// @return rcomp compounded interest rate from last update until now (1e18 == 100%)
/// @return ri current integral part of the rate
/// @return Tcrit time during which the utilization exceeds the critical value
function calculateCompoundInterestRate(
Config memory _c,
uint256 _totalDeposits,
uint256 _totalBorrowAmount,
uint256 _interestRateTimestamp,
uint256 _blockTimestamp
) external pure returns (
uint256 rcomp,
int256 ri,
int256 Tcrit // solhint-disable-line var-name-mixedcase
);
/// @dev returns decimal points used by model
function DP() external pure returns (uint256); // solhint-disable-line func-name-mixedcase
/// @dev just a helper method to see if address is a InterestRateModel
/// @return always true
function interestRateModelPing() external pure returns (bytes4);
}
LiquidationHelper.sol 558 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./magicians/interfaces/IMagician.sol";
import "../SiloLens.sol";
import "../interfaces/ISiloFactory.sol";
import "../interfaces/IPriceProviderV2.sol";
import "../interfaces/ISwapper.sol";
import "../interfaces/ISiloRepository.sol";
import "../interfaces/IPriceProvidersRepository.sol";
import "../interfaces/IWrappedNativeToken.sol";
import "../priceProviders/chainlinkV3/ChainlinkV3PriceProvider.sol";
import "../lib/Ping.sol";
import "../lib/RevertBytes.sol";
import "./ZeroExSwap.sol";
import "./lib/LiquidationScenarioDetector.sol";
import "./LiquidationRepay.sol";
/// @notice LiquidationHelper IS NOT PART OF THE PROTOCOL. SILO CREATED THIS TOOL, MOSTLY AS AN EXAMPLE.
/// see https://github.com/silo-finance/liquidation#readme for details how liquidation process should look like
contract LiquidationHelper is ILiquidationHelper, IFlashLiquidationReceiver, ZeroExSwap, LiquidationRepay, Ownable {
using RevertBytes for bytes;
using SafeERC20 for IERC20;
using Address for address payable;
using LiquidationScenarioDetector for LiquidationScenario;
struct MagicianConfig {
address asset;
IMagician magician;
}
struct SwapperConfig {
IPriceProvider provider;
ISwapper swapper;
}
uint256 immutable private _BASE_TX_COST; // solhint-disable-line var-name-mixedcase
ISiloRepository public immutable SILO_REPOSITORY; // solhint-disable-line var-name-mixedcase
IPriceProvidersRepository public immutable PRICE_PROVIDERS_REPOSITORY; // solhint-disable-line var-name-mixedcase
SiloLens public immutable LENS; // solhint-disable-line var-name-mixedcase
IERC20 public immutable QUOTE_TOKEN; // solhint-disable-line var-name-mixedcase
/// @dev token receiver will get all rewards from liquidation, does not matter who will execute tx
address payable public immutable TOKENS_RECEIVER; // solhint-disable-line var-name-mixedcase
ChainlinkV3PriceProvider public immutable CHAINLINK_PRICE_PROVIDER; // solhint-disable-line var-name-mixedcase
mapping(IPriceProvider => ISwapper) public swappers;
// asset => magician
mapping(address => IMagician) public magicians;
error InvalidSiloLens();
error InvalidSiloRepository();
error LiquidationNotProfitable(uint256 inTheRed);
error NotSilo();
error PriceProviderNotFound();
error FallbackPriceProviderNotSet();
error SwapperNotFound();
error MagicianNotFound();
error SwapAmountInFailed();
error SwapAmountOutFailed();
error UsersMustMatchSilos();
error InvalidChainlinkProviders();
error InvalidMagicianConfig();
error InvalidSwapperConfig();
error InvalidTowardsAssetConvertion();
error InvalidScenario();
error Max0xSwapsIs2();
event SwapperConfigured(IPriceProvider provider, ISwapper swapper);
event MagicianConfigured(address asset, IMagician magician);
/// @dev event emitted on user liquidation
/// @param silo Silo where liquidation happen
/// @param user User that been liquidated
/// @param earned amount of ETH earned (excluding gas cost)
/// @param estimatedEarnings for LiquidationScenario.Full0xWithChange `earned` amount is estimated,
/// because tokens were not sold for ETH inside transaction
event LiquidationExecuted(address indexed silo, address indexed user, uint256 earned, bool estimatedEarnings);
constructor (
address _repository,
address _chainlinkPriceProvider,
address _lens,
address _exchangeProxy,
MagicianConfig[] memory _magicians,
SwapperConfig[] memory _swappers,
uint256 _baseCost,
address payable _tokensReceiver
) ZeroExSwap(_exchangeProxy) {
if (!Ping.pong(SiloLens(_lens).lensPing)) revert InvalidSiloLens();
if (!Ping.pong(ISiloRepository(_repository).siloRepositoryPing)) {
revert InvalidSiloRepository();
}
SILO_REPOSITORY = ISiloRepository(_repository);
LENS = SiloLens(_lens);
// configure swappers
_configureSwappers(_swappers);
// configure magicians
_configureMagicians(_magicians);
PRICE_PROVIDERS_REPOSITORY = ISiloRepository(_repository).priceProvidersRepository();
CHAINLINK_PRICE_PROVIDER = ChainlinkV3PriceProvider(_chainlinkPriceProvider);
QUOTE_TOKEN = IERC20(PRICE_PROVIDERS_REPOSITORY.quoteToken());
_BASE_TX_COST = _baseCost;
TOKENS_RECEIVER = _tokensReceiver;
}
receive() external payable {}
function executeLiquidation(
address _user,
ISilo _silo,
LiquidationScenario _scenario,
SwapInput0x[] calldata _swapsInputs0x
) external {
if (_swapsInputs0x.length > 2) revert Max0xSwapsIs2();
uint256 gasStart = gasleft();
address[] memory users = new address[](1);
users[0] = _user;
_silo.flashLiquidate(users, abi.encode(gasStart, _scenario, _swapsInputs0x));
}
function setSwappers(SwapperConfig[] calldata _swappers) external onlyOwner {
_configureSwappers(_swappers);
}
function setMagicians(MagicianConfig[] calldata _magicians) external onlyOwner {
_configureMagicians(_magicians);
}
/// @notice this is working example of how to perform liquidation, this method will be called by Silo
/// Keep in mind, that this helper might NOT choose the best swap option.
/// For best results (highest earnings) you probably want to implement your own callback and maybe use some
/// dex aggregators.
/// @dev after liquidation we always send remaining tokens so contract should never has any leftover
function siloLiquidationCallback(
address _user,
address[] calldata _assets,
uint256[] calldata _receivedCollaterals,
uint256[] calldata _shareAmountsToRepaid,
bytes calldata _flashReceiverData
) external override {
if (!SILO_REPOSITORY.isSilo(msg.sender)) revert NotSilo();
address payable executor = TOKENS_RECEIVER;
(
uint256 gasStart,
LiquidationScenario scenario,
SwapInput0x[] memory swapInputs
) = abi.decode(_flashReceiverData, (uint256, LiquidationScenario, SwapInput0x[]));
if (swapInputs.length != 0) {
_execute0x(swapInputs);
}
uint256 earned = _siloLiquidationCallbackExecution(
scenario,
_user,
_assets,
_receivedCollaterals,
_shareAmountsToRepaid
);
// I needed to move some part of execution from from `_siloLiquidationCallbackExecution`,
// because of "stack too deep" error
bool estimatedEarnings = scenario.isFull0x() || scenario.isFull0xWithChange();
bool checkForProfit = scenario.calculateEarnings();
if (estimatedEarnings) {
earned = _estimateEarningsAndTransferChange(_assets, _shareAmountsToRepaid, executor, checkForProfit);
} else {
_transferNative(executor, earned);
}
emit LiquidationExecuted(msg.sender, _user, earned, estimatedEarnings);
// do not check for profitability when forcing
if (checkForProfit) {
ensureTxIsProfitable(gasStart, earned);
}
}
/// @dev This method should be used to made decision about `Full0x` vs `Full0xWithChange` liquidation scenario.
/// @return TRUE, if asset liquidation is supported internally, otherwise FALSE
function liquidationSupported(address _asset) external view returns (bool) {
if (_asset == address(QUOTE_TOKEN)) return true;
if (address(magicians[_asset]) != address(0)) return true;
try this.findPriceProvider(_asset) returns (IPriceProvider) {
return true;
} catch (bytes memory) {
// we do not care about reason
}
return false;
}
function checkSolvency(address[] calldata _users, ISilo[] calldata _silos) external view returns (bool[] memory) {
if (_users.length != _silos.length) revert UsersMustMatchSilos();
bool[] memory solvency = new bool[](_users.length);
for (uint256 i; i < _users.length;) {
solvency[i] = _silos[i].isSolvent(_users[i]);
// we will never have that many users to overflow
unchecked { i++; }
}
return solvency;
}
function checkDebt(address[] calldata _users, ISilo[] calldata _silos) external view returns (bool[] memory) {
bool[] memory hasDebt = new bool[](_users.length);
for (uint256 i; i < _users.length;) {
hasDebt[i] = LENS.inDebt(_silos[i], _users[i]);
// we will never have that many users to overflow
unchecked { i++; }
}
return hasDebt;
}
function ensureTxIsProfitable(uint256 _gasStart, uint256 _earnedEth) public view returns (uint256 txFee) {
unchecked {
// gas calculation will not overflow because values are never that high
// `gasStart` is external value, but it value that we initiating and Silo contract passing it to us
uint256 gasSpent = _gasStart - gasleft() + _BASE_TX_COST;
txFee = tx.gasprice * gasSpent;
if (txFee > _earnedEth) {
// it will not underflow because we check above
revert LiquidationNotProfitable(txFee - _earnedEth);
}
}
}
function findPriceProvider(address _asset) public view returns (IPriceProvider priceProvider) {
priceProvider = PRICE_PROVIDERS_REPOSITORY.priceProviders(_asset);
if (address(priceProvider) == address(0)) revert PriceProviderNotFound();
// check for backwards compatibility with chainlink provider
if (priceProvider == CHAINLINK_PRICE_PROVIDER) {
priceProvider = CHAINLINK_PRICE_PROVIDER.getFallbackProvider(_asset);
if (address(priceProvider) == address(0)) revert FallbackPriceProviderNotSet();
return priceProvider;
}
// only IPriceProviderV2 has `IPriceProviderV2()`
try IPriceProviderV2(address(priceProvider)).offChainProvider() returns (bool isOffChainProvider) {
if (isOffChainProvider) {
priceProvider = IPriceProviderV2(address(priceProvider)).getFallbackProvider(_asset);
if (address(priceProvider) == address(0)) revert FallbackPriceProviderNotSet();
}
} catch (bytes memory) {}
}
function _execute0x(SwapInput0x[] memory _swapInputs) internal {
for (uint256 i; i < _swapInputs.length;) {
fillQuote(_swapInputs[i].sellToken, _swapInputs[i].allowanceTarget, _swapInputs[i].swapCallData);
// we can not have that much data in array to overflow
unchecked { i++; }
}
}
function _siloLiquidationCallbackExecution(
LiquidationScenario _scenario,
address _user,
address[] calldata _assets,
uint256[] calldata _receivedCollaterals,
uint256[] calldata _shareAmountsToRepaid
) internal returns (uint256 earned) {
if (_scenario.isFull0x() || _scenario.isFull0xWithChange()) {
// we should have repay tokens ready to go
_repay(ISilo(msg.sender), _user, _assets, _shareAmountsToRepaid);
// change that left after repay will be send to `TOKENS_RECEIVER` by `_estimateEarningsAndTransferChange`
return 0;
}
if (_scenario.isInternal()) {
return _runInternalScenario(
_user,
_assets,
_receivedCollaterals,
_shareAmountsToRepaid
);
}
if (_scenario.isCollateral0x()) {
return _runCollateral0xScenario(
_user,
_assets,
_shareAmountsToRepaid
);
}
revert InvalidScenario();
}
function _runCollateral0xScenario(
address _user,
address[] calldata _assets,
uint256[] calldata _shareAmountsToRepaid
) internal returns (uint256 earned) {
// we have WETH, we need to deal with swap WETH -> repay asset internally
_swapWrappedNativeForRepayAssets(_assets, _shareAmountsToRepaid);
_repay(ISilo(msg.sender), _user, _assets, _shareAmountsToRepaid);
earned = QUOTE_TOKEN.balanceOf(address(this));
}
function _runInternalScenario(
address _user,
address[] calldata _assets,
uint256[] calldata _receivedCollaterals,
uint256[] calldata _shareAmountsToRepaid
) internal returns (uint256 earned) {
uint256 quoteAmountFromCollaterals = _swapAllForQuote(_assets, _receivedCollaterals);
uint256 quoteSpentOnRepay = _swapWrappedNativeForRepayAssets(_assets, _shareAmountsToRepaid);
_repay(ISilo(msg.sender), _user, _assets, _shareAmountsToRepaid);
earned = quoteAmountFromCollaterals - quoteSpentOnRepay;
}
function _estimateEarningsAndTransferChange(
address[] calldata _assets,
uint256[] calldata _shareAmountsToRepaid,
address payable _liquidator,
bool _returnEarnedAmount
) internal returns (uint256 earned) {
// change that left after repay will be send to `_liquidator`
for (uint256 i = 0; i < _assets.length;) {
if (_shareAmountsToRepaid[i] != 0) {
uint256 amount = IERC20(_assets[i]).balanceOf(address(this));
if (_assets[i] == address(QUOTE_TOKEN)) {
if (_returnEarnedAmount) {
// balance will not overflow
unchecked { earned += amount; }
}
_transferNative(_liquidator, amount);
} else {
if (_returnEarnedAmount) {
// we processing numbers that Silo created, if Silo did not over/under flow, we will not as well
unchecked { earned += amount * PRICE_PROVIDERS_REPOSITORY.getPrice(_assets[i]) / 1e18; }
}
IERC20(_assets[i]).transfer(_liquidator, amount);
}
}
// we will never have that many assets to overflow
unchecked { i++; }
}
}
function _swapAllForQuote(
address[] calldata _assets,
uint256[] calldata _receivedCollaterals
) internal returns (uint256 quoteAmount) {
// swap all for quote token
unchecked {
// we will not overflow with `i` in a lifetime
for (uint256 i = 0; i < _assets.length; i++) {
// if silo was able to handle solvency calculations, then we can handle quoteAmount without safe math
quoteAmount += _swapForQuote(_assets[i], _receivedCollaterals[i]);
}
}
}
function _swapWrappedNativeForRepayAssets(
address[] calldata _assets,
uint256[] calldata _shareAmountsToRepaid
) internal returns (uint256 quoteSpendOnRepay) {
for (uint256 i = 0; i < _assets.length;) {
if (_shareAmountsToRepaid[i] != 0) {
// if silo was able to handle solvency calculations, then we can handle amounts without safe math here
unchecked {
quoteSpendOnRepay += _swapForAsset(_assets[i], _shareAmountsToRepaid[i]);
}
}
// we will never have that many assets to overflow
unchecked { i++; }
}
}
/// @notice We assume that quoteToken is wrapped native token
function _transferNative(address payable _to, uint256 _amount) internal {
IWrappedNativeToken(address(QUOTE_TOKEN)).withdraw(_amount);
_to.sendValue(_amount);
}
/// @dev it swaps asset token for quote
/// @param _asset address
/// @param _amount exact amount of asset to swap
/// @return amount of quote token
function _swapForQuote(address _asset, uint256 _amount) internal returns (uint256) {
address quoteToken = address(QUOTE_TOKEN);
if (_amount == 0 || _asset == quoteToken) return _amount;
address magician = address(magicians[_asset]);
if (magician != address(0)) {
bytes memory result = _safeDelegateCall(
magician,
abi.encodeCall(IMagician.towardsNative, (_asset, _amount)),
"towardsNativeFailed"
);
(address tokenOut, uint256 amountOut) = abi.decode(result, (address, uint256));
return _swapForQuote(tokenOut, amountOut);
}
(IPriceProvider provider, ISwapper swapper) = _resolveProviderAndSwapper(_asset);
// no need for safe approval, because we always using 100%
// Low level call needed to support non-standard `ERC20.approve` eg like `USDT.approve`
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = _asset.call(abi.encodeCall(IERC20.approve, (swapper.spenderToApprove(), _amount)));
if (!success) revert ApprovalFailed();
bytes memory callData = abi.encodeCall(ISwapper.swapAmountIn, (
_asset, quoteToken, _amount, address(provider), _asset
));
bytes memory data = _safeDelegateCall(address(swapper), callData, "swapAmountIn");
return abi.decode(data, (uint256));
}
/// @dev it swaps quote token for asset
/// @param _asset address
/// @param _amount exact amount OUT, what we want to receive
/// @return amount of quote token used for swap
function _swapForAsset(address _asset, uint256 _amount) internal returns (uint256) {
address quoteToken = address(QUOTE_TOKEN);
if (_amount == 0 || quoteToken == _asset) return _amount;
address magician = address(magicians[_asset]);
if (magician != address(0)) {
bytes memory result = _safeDelegateCall(
magician,
abi.encodeCall(IMagician.towardsAsset, (_asset, _amount)),
"towardsAssetFailed"
);
(address tokenOut, uint256 amountOut) = abi.decode(result, (address, uint256));
// towardsAsset should convert to `_asset`
if (tokenOut != _asset) revert InvalidTowardsAssetConvertion();
return amountOut;
}
(IPriceProvider provider, ISwapper swapper) = _resolveProviderAndSwapper(_asset);
address spender = swapper.spenderToApprove();
IERC20(quoteToken).approve(spender, type(uint256).max);
bytes memory callData = abi.encodeCall(ISwapper.swapAmountOut, (
quoteToken, _asset, _amount, address(provider), _asset
));
bytes memory data = _safeDelegateCall(address(swapper), callData, "SwapAmountOutFailed");
IERC20(quoteToken).approve(spender, 0);
return abi.decode(data, (uint256));
}
function _resolveProviderAndSwapper(address _asset) internal view returns (IPriceProvider, ISwapper) {
IPriceProvider priceProvider = findPriceProvider(_asset);
ISwapper swapper = _resolveSwapper(priceProvider);
return (priceProvider, swapper);
}
function _resolveSwapper(IPriceProvider priceProvider) internal view returns (ISwapper) {
ISwapper swapper = swappers[priceProvider];
if (address(swapper) == address(0)) {
revert SwapperNotFound();
}
return swapper;
}
function _safeDelegateCall(
address _target,
bytes memory _callData,
string memory _mgs
)
internal
returns (bytes memory data)
{
bool success;
// solhint-disable-next-line avoid-low-level-calls
(success, data) = address(_target).delegatecall(_callData);
if (!success || data.length == 0) data.revertBytes(_mgs);
}
function _configureSwappers(SwapperConfig[] memory _swappers) internal {
for (uint256 i = 0; i < _swappers.length; i++) {
IPriceProvider provider = _swappers[i].provider;
ISwapper swapper = _swappers[i].swapper;
if (address(provider) == address(0) || address(swapper) == address(0)) {
revert InvalidSwapperConfig();
}
swappers[provider] = swapper;
emit SwapperConfigured(provider, swapper);
}
}
function _configureMagicians(MagicianConfig[] memory _magicians) internal {
for (uint256 i = 0; i < _magicians.length; i++) {
address asset = _magicians[i].asset;
IMagician magician = _magicians[i].magician;
if (asset == address(0) || address(magician) == address(0)) {
revert InvalidMagicianConfig();
}
magicians[asset] = magician;
emit MagicianConfigured(asset, magician);
}
}
}
IWrappedNativeToken.sol 9 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IWrappedNativeToken is IERC20 {
function deposit() external payable;
function withdraw(uint256 amount) external;
}
ERC20.sol 356 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.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 Contracts guidelines: functions revert
* instead 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, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The default value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override 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 this function is
* overridden;
*
* 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 override returns (uint8) {
return 18;
}
/**
* @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);
uint256 currentAllowance = _allowances[sender][_msgSender()];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, _msgSender(), currentAllowance - amount);
}
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] + 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) {
uint256 currentAllowance = _allowances[_msgSender()][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(_msgSender(), spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `sender` to `recipient`.
*
* This 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);
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
_afterTokenTransfer(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:
*
* - `account` 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 += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
_afterTokenTransfer(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);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
_afterTokenTransfer(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 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 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 {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been 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 _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
IERC20.sol 82 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
pragma solidity ^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);
}
INotificationReceiver.sol 16 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.13;
/// @title Common interface for Silo Incentive Contract
interface INotificationReceiver {
/// @dev Informs the contract about token transfer
/// @param _token address of the token that was transferred
/// @param _from sender
/// @param _to receiver
/// @param _amount amount that was transferred
function onAfterTransfer(address _token, address _from, address _to, uint256 _amount) external;
/// @dev Sanity check function
/// @return always true
function notificationReceiverPing() external pure returns (bytes4);
}
SafeMath.sol 227 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
* now has built in overflow checking.
*/
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) {
unchecked {
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) {
unchecked {
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) {
unchecked {
// 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) {
unchecked {
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) {
unchecked {
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) {
return a + b;
}
/**
* @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) {
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) {
return a * b;
}
/**
* @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.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
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) {
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) {
unchecked {
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.
*
* 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) {
unchecked {
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) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}
IFlashLiquidationReceiver.sol 26 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
/// @dev when performing Silo flash liquidation, FlashReceiver contract will receive all collaterals
interface IFlashLiquidationReceiver {
/// @dev this method is called when doing Silo flash liquidation
/// one can NOT assume, that if _seizedCollateral[i] != 0, then _shareAmountsToRepaid[i] must be 0
/// one should assume, that any combination of amounts is possible
/// on callback, one must call `Silo.repayFor` because at the end of transaction,
/// Silo will check if borrower is solvent.
/// @param _user user address, that is liquidated
/// @param _assets array of collateral assets received during user liquidation
/// this array contains all assets (collateral borrowed) without any order
/// @param _receivedCollaterals array of collateral amounts received during user liquidation
/// indexes of amounts are related to `_assets`,
/// @param _shareAmountsToRepaid array of amounts to repay for each asset
/// indexes of amounts are related to `_assets`,
/// @param _flashReceiverData data that are passed from sender that executes liquidation
function siloLiquidationCallback(
address _user,
address[] calldata _assets,
uint256[] calldata _receivedCollaterals,
uint256[] calldata _shareAmountsToRepaid,
bytes memory _flashReceiverData
) external;
}
IPriceProvidersRepository.sol 73 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6 <0.9.0;
import "./IPriceProvider.sol";
interface IPriceProvidersRepository {
/// @notice Emitted when price provider is added
/// @param newPriceProvider new price provider address
event NewPriceProvider(IPriceProvider indexed newPriceProvider);
/// @notice Emitted when price provider is removed
/// @param priceProvider removed price provider address
event PriceProviderRemoved(IPriceProvider indexed priceProvider);
/// @notice Emitted when asset is assigned to price provider
/// @param asset assigned asset address
/// @param priceProvider price provider address
event PriceProviderForAsset(address indexed asset, IPriceProvider indexed priceProvider);
/// @notice Register new price provider
/// @param _priceProvider address of price provider
function addPriceProvider(IPriceProvider _priceProvider) external;
/// @notice Unregister price provider
/// @param _priceProvider address of price provider to be removed
function removePriceProvider(IPriceProvider _priceProvider) external;
/// @notice Sets price provider for asset
/// @dev Request for asset price is forwarded to the price provider assigned to that asset
/// @param _asset address of an asset for which price provider will be used
/// @param _priceProvider address of price provider
function setPriceProviderForAsset(address _asset, IPriceProvider _priceProvider) external;
/// @notice Returns "Time-Weighted Average Price" for an asset
/// @param _asset address of an asset for which to read price
/// @return price TWAP price of a token with 18 decimals
function getPrice(address _asset) external view returns (uint256 price);
/// @notice Gets price provider assigned to an asset
/// @param _asset address of an asset for which to get price provider
/// @return priceProvider address of price provider
function priceProviders(address _asset) external view returns (IPriceProvider priceProvider);
/// @notice Gets token address in which prices are quoted
/// @return quoteToken address
function quoteToken() external view returns (address);
/// @notice Gets manager role address
/// @return manager role address
function manager() external view returns (address);
/// @notice Checks if providers are available for an asset
/// @param _asset asset address to check
/// @return returns TRUE if price feed is ready, otherwise false
function providersReadyForAsset(address _asset) external view returns (bool);
/// @notice Returns true if address is a registered price provider
/// @param _provider address of price provider to be removed
/// @return true if address is a registered price provider, otherwise false
function isPriceProvider(IPriceProvider _provider) external view returns (bool);
/// @notice Gets number of price providers registered
/// @return number of price providers registered
function providersCount() external view returns (uint256);
/// @notice Gets an array of price providers
/// @return array of price providers
function providerList() external view returns (address[] memory);
/// @notice Sanity check function
/// @return returns always TRUE
function priceProvidersRepositoryPing() external pure returns (bytes4);
}
ILiquidationHelper.sol 25 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
/// @notice LiquidationHelper IS NOT PART OF THE PROTOCOL. SILO CREATED THIS TOOL, MOSTLY AS AN EXAMPLE.
interface ILiquidationHelper {
/// @dev Liquidation scenarios that are supported by helper:
/// - Internal: fully on-chain, using internal swappers, magicians etc
/// When 0x API can not handle the swap, we will use internal.
/// - Full0x: 0x will handle swap for collateral -> repay asset, then contract needs to do repay.
/// Change that left after repay will be swapped to WETH using internal methods.
/// This scenario is for A -> B or A, B -> C cases.
/// - Full0xWithChange: similar to Full0x, but all repay tokens that left, will be send to liquidator.
/// BE bot needs to do another tx to swap change to ETH
/// This scenario is for A -> B or A, B -> C cases
/// Exception: WETH -> A, it should be full or internal
/// Helper is supporting all the tokens internally, so only case, when we would need Full0xWithChange is when
/// we didn't develop swapper/magician for some new asset yet. Call `liquidationSupported` to check it.
/// - Collateral0x: 0x will swap collateral to native token, then from native -> repay asset contract handle it
/// This is for A -> XAI, WETH, other cases of multiple repay tokens are not supported by 0x
/// - *Force: force option allows to liquidate even when liquidation is not profitable
enum LiquidationScenario {
Internal, Collateral0x, Full0x, Full0xWithChange,
InternalForce, Collateral0xForce, Full0xForce, Full0xWithChangeForce
}
}
SafeERC20.sol 99 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.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 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'
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) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_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
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
IMagician.sol 19 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9.0;
/// @notice Extension for the Liquidation helper to support such operations as unwrapping
interface IMagician {
/// @notice Operates to unwrap an `_asset`
/// @param _asset Asset to be unwrapped
/// @param _amount Amount of the `_asset`
/// @return tokenOut A token that the `_asset` has been converted to
/// @return amountOut Amount of the `tokenOut` that we received
function towardsNative(address _asset, uint256 _amount) external returns (address tokenOut, uint256 amountOut);
/// @notice Performs operation opposit to `towardsNative`
/// @param _asset Asset to be wrapped
/// @param _amount Amount of the `_asset`
/// @return tokenOut A token that the `_asset` has been converted to
/// @return amountOut Amount of the quote token that we spent to get `_amoun` of the `_asset`
function towardsAsset(address _asset, uint256 _amount) external returns (address tokenOut, uint256 amountOut);
}
LiquidationScenarioDetector.sol 34 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "../interface/ILiquidationHelper.sol";
/// @notice Library for processing LiquidationScenarios data
library LiquidationScenarioDetector {
function isFull0x(ILiquidationHelper.LiquidationScenario _scenario) internal pure returns (bool) {
return _scenario == ILiquidationHelper.LiquidationScenario.Full0x
|| _scenario == ILiquidationHelper.LiquidationScenario.Full0xForce;
}
function isFull0xWithChange(ILiquidationHelper.LiquidationScenario _scenario) internal pure returns (bool) {
return _scenario == ILiquidationHelper.LiquidationScenario.Full0xWithChange
|| _scenario == ILiquidationHelper.LiquidationScenario.Full0xWithChangeForce;
}
function isCollateral0x(ILiquidationHelper.LiquidationScenario _scenario) internal pure returns (bool) {
return _scenario == ILiquidationHelper.LiquidationScenario.Collateral0x
|| _scenario == ILiquidationHelper.LiquidationScenario.Collateral0xForce;
}
function isInternal(ILiquidationHelper.LiquidationScenario _scenario) internal pure returns (bool) {
return _scenario == ILiquidationHelper.LiquidationScenario.Internal
|| _scenario == ILiquidationHelper.LiquidationScenario.InternalForce;
}
function calculateEarnings(ILiquidationHelper.LiquidationScenario _scenario) internal pure returns (bool) {
return _scenario == ILiquidationHelper.LiquidationScenario.Internal
|| _scenario == ILiquidationHelper.LiquidationScenario.Collateral0x
|| _scenario == ILiquidationHelper.LiquidationScenario.Full0x
|| _scenario == ILiquidationHelper.LiquidationScenario.Full0xWithChange;
}
}
IERC20Metadata.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
ChainlinkV3PriceProvider.sol 436 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../PriceProvider.sol";
import "../IERC20LikeV2.sol";
contract ChainlinkV3PriceProvider is PriceProvider {
using SafeMath for uint256;
struct AssetData {
// Time threshold to invalidate stale prices
uint256 heartbeat;
// If true, we bypass the aggregator and consult the fallback provider directly
bool forceFallback;
// If true, the aggregator returns price in USD, so we need to convert to QUOTE
bool convertToQuote;
// Chainlink aggregator proxy
AggregatorV3Interface aggregator;
// Provider used if the aggregator's price is invalid or if it became disabled
IPriceProvider fallbackProvider;
}
/// @dev Aggregator that converts from USD to quote token
AggregatorV3Interface internal immutable _QUOTE_AGGREGATOR; // solhint-disable-line var-name-mixedcase
/// @dev Decimals used by the _QUOTE_AGGREGATOR
uint8 internal immutable _QUOTE_AGGREGATOR_DECIMALS; // solhint-disable-line var-name-mixedcase
/// @dev Used to optimize calculations in emergency disable function
// solhint-disable-next-line var-name-mixedcase
uint256 internal immutable _MAX_PRICE_DIFF = type(uint256).max / (100 * EMERGENCY_PRECISION);
// @dev Precision to use for the EMERGENCY_THRESHOLD
uint256 public constant EMERGENCY_PRECISION = 1e6;
/// @dev Disable the aggregator if the difference with the fallback is higher than this percentage (10%)
uint256 public constant EMERGENCY_THRESHOLD = 10 * EMERGENCY_PRECISION; // solhint-disable-line var-name-mixedcase
/// @dev this is basically `PriceProvider.quoteToken.decimals()`
uint8 internal immutable _QUOTE_TOKEN_DECIMALS; // solhint-disable-line var-name-mixedcase
/// @dev Address allowed to call the emergencyDisable function, can be set by the owner
address public emergencyManager;
/// @dev Threshold used to determine if the price returned by the _QUOTE_AGGREGATOR is valid
uint256 public quoteAggregatorHeartbeat;
/// @dev Data used for each asset
mapping(address => AssetData) public assetData;
event NewAggregator(address indexed asset, AggregatorV3Interface indexed aggregator, bool convertToQuote);
event NewFallbackPriceProvider(address indexed asset, IPriceProvider indexed fallbackProvider);
event NewHeartbeat(address indexed asset, uint256 heartbeat);
event NewQuoteAggregatorHeartbeat(uint256 heartbeat);
event NewEmergencyManager(address indexed emergencyManager);
event AggregatorDisabled(address indexed asset, AggregatorV3Interface indexed aggregator);
error AggregatorDidNotChange();
error AggregatorPriceNotAvailable();
error AssetNotSupported();
error EmergencyManagerDidNotChange();
error EmergencyThresholdNotReached();
error FallbackProviderAlreadySet();
error FallbackProviderDidNotChange();
error FallbackProviderNotSet();
error HeartbeatDidNotChange();
error InvalidAggregator();
error InvalidAggregatorDecimals();
error InvalidFallbackPriceProvider();
error InvalidHeartbeat();
error OnlyEmergencyManager();
error QuoteAggregatorHeartbeatDidNotChange();
modifier onlyAssetSupported(address _asset) {
if (!assetSupported(_asset)) {
revert AssetNotSupported();
}
_;
}
constructor(
IPriceProvidersRepository _priceProvidersRepository,
address _emergencyManager,
AggregatorV3Interface _quoteAggregator,
uint256 _quoteAggregatorHeartbeat
) PriceProvider(_priceProvidersRepository) {
_setEmergencyManager(_emergencyManager);
_QUOTE_TOKEN_DECIMALS = IERC20LikeV2(quoteToken).decimals();
_QUOTE_AGGREGATOR = _quoteAggregator;
_QUOTE_AGGREGATOR_DECIMALS = _quoteAggregator.decimals();
quoteAggregatorHeartbeat = _quoteAggregatorHeartbeat;
}
/// @inheritdoc IPriceProvider
function assetSupported(address _asset) public view virtual override returns (bool) {
AssetData storage data = assetData[_asset];
// Asset is supported if:
// - the asset is the quote token
// OR
// - the aggregator address is defined AND
// - the aggregator is not disabled
// OR
// - the fallback is defined
if (_asset == quoteToken) {
return true;
}
if (address(data.aggregator) != address(0)) {
return !data.forceFallback || address(data.fallbackProvider) != address(0);
}
return false;
}
/// @dev Returns price directly from aggregator using all internal settings except of fallback provider
/// @param _asset Asset for which we want to get the price
function getAggregatorPrice(address _asset) public view virtual returns (bool success, uint256 price) {
(success, price) = _getAggregatorPrice(_asset);
}
/// @inheritdoc IPriceProvider
function getPrice(address _asset) public view virtual override returns (uint256) {
address quote = quoteToken;
if (_asset == quote) {
return 10 ** _QUOTE_TOKEN_DECIMALS;
}
(bool success, uint256 price) = _getAggregatorPrice(_asset);
return success ? price : _getFallbackPrice(_asset);
}
/// @dev Sets the aggregator, fallbackProvider and heartbeat for an asset. Can only be called by the manager.
/// @param _asset Asset to setup
/// @param _aggregator Chainlink aggregator proxy
/// @param _fallbackProvider Provider to use if the price is invalid or if the aggregator was disabled
/// @param _heartbeat Threshold in seconds to invalidate a stale price
function setupAsset(
address _asset,
AggregatorV3Interface _aggregator,
IPriceProvider _fallbackProvider,
uint256 _heartbeat,
bool _convertToQuote
) external virtual onlyManager {
// This has to be done first so that `_setAggregator` works
_setHeartbeat(_asset, _heartbeat);
if (!_setAggregator(_asset, _aggregator, _convertToQuote)) revert AggregatorDidNotChange();
// We don't care if this doesn't change
_setFallbackPriceProvider(_asset, _fallbackProvider);
}
/// @dev Sets the aggregator for an asset. Can only be called by the manager.
/// @param _asset Asset for which to set the aggregator
/// @param _aggregator Aggregator to set
function setAggregator(address _asset, AggregatorV3Interface _aggregator, bool _convertToQuote)
external
virtual
onlyManager
onlyAssetSupported(_asset)
{
if (!_setAggregator(_asset, _aggregator, _convertToQuote)) revert AggregatorDidNotChange();
}
/// @dev Sets the fallback provider for an asset. Can only be called by the manager.
/// @param _asset Asset for which to set the fallback provider
/// @param _fallbackProvider Provider to set
function setFallbackPriceProvider(address _asset, IPriceProvider _fallbackProvider)
external
virtual
onlyManager
onlyAssetSupported(_asset)
{
if (!_setFallbackPriceProvider(_asset, _fallbackProvider)) {
revert FallbackProviderDidNotChange();
}
}
/// @dev Sets the heartbeat threshold for an asset. Can only be called by the manager.
/// @param _asset Asset for which to set the heartbeat threshold
/// @param _heartbeat Threshold to set
function setHeartbeat(address _asset, uint256 _heartbeat)
external
virtual
onlyManager
onlyAssetSupported(_asset)
{
if (!_setHeartbeat(_asset, _heartbeat)) revert HeartbeatDidNotChange();
}
/// @dev Sets the quote aggregator heartbeat threshold. Can only be called by the manager.
/// @param _heartbeat Threshold to set
function setQuoteAggregatorHeartbeat(uint256 _heartbeat)
external
virtual
onlyManager
{
if (!_setQuoteAggregatorHeartbeat(_heartbeat)) revert QuoteAggregatorHeartbeatDidNotChange();
}
/// @dev Sets the emergencyManager. Can only be called by the manager.
/// @param _emergencyManager Emergency manager to set
function setEmergencyManager(address _emergencyManager) external virtual onlyManager {
if (!_setEmergencyManager(_emergencyManager)) revert EmergencyManagerDidNotChange();
}
/// @dev Disables the aggregator for an asset if there is a big discrepancy between the aggregator and the
/// fallback provider. The only way to reenable the asset is by calling setupAsset or setAggregator again.
/// Can only be called by the emergencyManager.
/// @param _asset Asset for which to disable the aggregator
function emergencyDisable(address _asset) external virtual {
if (msg.sender != emergencyManager) {
revert OnlyEmergencyManager();
}
(bool success, uint256 price) = _getAggregatorPrice(_asset);
if (!success) {
revert AggregatorPriceNotAvailable();
}
uint256 fallbackPrice = _getFallbackPrice(_asset);
uint256 diff;
unchecked {
// It is ok to uncheck because of the initial fallbackPrice >= price check
diff = fallbackPrice >= price ? fallbackPrice - price : price - fallbackPrice;
}
if (diff > _MAX_PRICE_DIFF || (diff * 100 * EMERGENCY_PRECISION) / price < EMERGENCY_THRESHOLD) {
revert EmergencyThresholdNotReached();
}
// Disable main aggregator, fallback stays enabled
assetData[_asset].forceFallback = true;
emit AggregatorDisabled(_asset, assetData[_asset].aggregator);
}
function getFallbackProvider(address _asset) external view virtual returns (IPriceProvider) {
return assetData[_asset].fallbackProvider;
}
function _getAggregatorPrice(address _asset) internal view virtual returns (bool success, uint256 price) {
AssetData storage data = assetData[_asset];
uint256 heartbeat = data.heartbeat;
bool forceFallback = data.forceFallback;
AggregatorV3Interface aggregator = data.aggregator;
if (address(aggregator) == address(0)) revert AssetNotSupported();
(
/*uint80 roundID*/,
int256 aggregatorPrice,
/*uint256 startedAt*/,
uint256 timestamp,
/*uint80 answeredInRound*/
) = aggregator.latestRoundData();
// If a valid price is returned and it was updated recently
if (!forceFallback && _isValidPrice(aggregatorPrice, timestamp, heartbeat)) {
uint256 result;
if (data.convertToQuote) {
// _toQuote performs decimal normalization internally
result = _toQuote(uint256(aggregatorPrice));
} else {
uint8 aggregatorDecimals = aggregator.decimals();
result = _normalizeWithDecimals(uint256(aggregatorPrice), aggregatorDecimals);
}
return (true, result);
}
return (false, 0);
}
function _getFallbackPrice(address _asset) internal view virtual returns (uint256) {
IPriceProvider fallbackProvider = assetData[_asset].fallbackProvider;
if (address(fallbackProvider) == address(0)) revert FallbackProviderNotSet();
return fallbackProvider.getPrice(_asset);
}
function _setEmergencyManager(address _emergencyManager) internal virtual returns (bool changed) {
if (_emergencyManager == emergencyManager) {
return false;
}
emergencyManager = _emergencyManager;
emit NewEmergencyManager(_emergencyManager);
return true;
}
function _setAggregator(
address _asset,
AggregatorV3Interface _aggregator,
bool _convertToQuote
) internal virtual returns (bool changed) {
if (address(_aggregator) == address(0)) revert InvalidAggregator();
AssetData storage data = assetData[_asset];
if (data.aggregator == _aggregator && data.forceFallback == false) {
return false;
}
// There doesn't seem to be a way to verify if this is a "valid" aggregator (other than getting the price)
data.forceFallback = false;
data.aggregator = _aggregator;
(bool success,) = _getAggregatorPrice(_asset);
if (!success) revert AggregatorPriceNotAvailable();
if (_convertToQuote && _aggregator.decimals() != _QUOTE_AGGREGATOR_DECIMALS) {
revert InvalidAggregatorDecimals();
}
// We want to always update this
assetData[_asset].convertToQuote = _convertToQuote;
emit NewAggregator(_asset, _aggregator, _convertToQuote);
return true;
}
function _setFallbackPriceProvider(address _asset, IPriceProvider _fallbackProvider)
internal
virtual
returns (bool changed)
{
if (_fallbackProvider == assetData[_asset].fallbackProvider) {
return false;
}
assetData[_asset].fallbackProvider = _fallbackProvider;
if (address(_fallbackProvider) != address(0)) {
if (
!priceProvidersRepository.isPriceProvider(_fallbackProvider) ||
!_fallbackProvider.assetSupported(_asset) ||
_fallbackProvider.quoteToken() != quoteToken
) {
revert InvalidFallbackPriceProvider();
}
// Make sure it doesn't revert
_getFallbackPrice(_asset);
}
emit NewFallbackPriceProvider(_asset, _fallbackProvider);
return true;
}
function _setHeartbeat(address _asset, uint256 _heartbeat) internal virtual returns (bool changed) {
// Arbitrary limit, Chainlink's threshold is always less than a day
if (_heartbeat > 2 days) revert InvalidHeartbeat();
if (_heartbeat == assetData[_asset].heartbeat) {
return false;
}
assetData[_asset].heartbeat = _heartbeat;
emit NewHeartbeat(_asset, _heartbeat);
return true;
}
function _setQuoteAggregatorHeartbeat(uint256 _heartbeat) internal virtual returns (bool changed) {
// Arbitrary limit, Chainlink's threshold is always less than a day
if (_heartbeat > 2 days) revert InvalidHeartbeat();
if (_heartbeat == quoteAggregatorHeartbeat) {
return false;
}
quoteAggregatorHeartbeat = _heartbeat;
emit NewQuoteAggregatorHeartbeat(_heartbeat);
return true;
}
/// @dev Adjusts the given price to use the same decimals as the quote token.
/// @param _price Price to adjust decimals
/// @param _decimals Decimals considered in `_price`
function _normalizeWithDecimals(uint256 _price, uint8 _decimals) internal view virtual returns (uint256) {
// We want to return the price of 1 asset token, but with the decimals of the quote token
if (_QUOTE_TOKEN_DECIMALS == _decimals) {
return _price;
} else if (_QUOTE_TOKEN_DECIMALS < _decimals) {
return _price / 10 ** (_decimals - _QUOTE_TOKEN_DECIMALS);
} else {
return _price * 10 ** (_QUOTE_TOKEN_DECIMALS - _decimals);
}
}
/// @dev Converts a price returned by an aggregator to quote units
function _toQuote(uint256 _price) internal view virtual returns (uint256) {
(
/*uint80 roundID*/,
int256 aggregatorPrice,
/*uint256 startedAt*/,
uint256 timestamp,
/*uint80 answeredInRound*/
) = _QUOTE_AGGREGATOR.latestRoundData();
// If an invalid price is returned
if (!_isValidPrice(aggregatorPrice, timestamp, quoteAggregatorHeartbeat)) {
revert AggregatorPriceNotAvailable();
}
// _price and aggregatorPrice should both have the same decimals so we normalize here
return _price * 10 ** _QUOTE_TOKEN_DECIMALS / uint256(aggregatorPrice);
}
function _isValidPrice(int256 _price, uint256 _timestamp, uint256 _heartbeat) internal view virtual returns (bool) {
return _price > 0 && block.timestamp - _timestamp < _heartbeat;
}
}
AggregatorV3Interface.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
Read Contract
CHAINLINK_PRICE_PROVIDER 0x39060ea4 → address
EXCHANGE_PROXY 0x6fca4f8f → address
LENS 0x18a4619a → address
PRICE_PROVIDERS_REPOSITORY 0x6f0ad2ca → address
QUOTE_TOKEN 0x78892cea → address
SILO_REPOSITORY 0xa7e8489d → address
TOKENS_RECEIVER 0xb34cbaf7 → address
checkDebt 0x03a69c9a → bool[]
checkSolvency 0xdde64303 → bool[]
ensureTxIsProfitable 0xa143e5f7 → uint256
findPriceProvider 0x94fa3add → address
liquidationSupported 0x338799b0 → bool
magicians 0xbe520039 → address
owner 0x8da5cb5b → address
swappers 0x8cad7fbe → address
Write Contract 7 functions
These functions modify contract state and require a wallet transaction to execute.
executeLiquidation 0x7ee742db
address _user
address _silo
uint8 _scenario
tuple[] _swapsInputs0x
fillQuote 0x8cf16261
address _sellToken
address _spender
bytes _swapCallData
renounceOwnership 0x715018a6
No parameters
setMagicians 0x2fa1e4eb
tuple[] _magicians
setSwappers 0x5f991f88
tuple[] _swappers
siloLiquidationCallback 0xe7b43da5
address _user
address[] _assets
uint256[] _receivedCollaterals
uint256[] _shareAmountsToRepaid
bytes _flashReceiverData
transferOwnership 0xf2fde38b
address newOwner
Recent Transactions
No transactions found for this address