Address Contract Verified
Address
0xCAB3D3d0d5544145A6BCB47e58F61368BCcAe2dB
Balance
0 ETH
Nonce
1
Code Size
13112 bytes
Creator
0xa31d555b...84CF at tx 0x1372827a...730a2a
Indexed Transactions
0 (1 on-chain, 1.2% indexed)
Contract Bytecode
13112 bytes
0x608060405234801561001057600080fd5b50600436106100575760003560e01c806327b4b4bf1461005c578063ac9650d81461008a578063b6f86321146100aa578063ef5cfb8c146100ef578063f2c11da514610104575b600080fd5b61006f61006a36600461287f565b610117565b60405161008196959493929190612910565b60405180910390f35b61009d610098366004612a06565b610df1565b6040516100819190612a97565b6100bd6100b8366004612b06565b610ee6565b604051610081949392919093151584526001600160a01b03928316602085015291166040830152606082015260800190565b6101026100fd36600461287f565b6113fc565b005b610102610112366004612b3f565b611682565b6060806060806060806000876001600160a01b031663dffeadd06040518163ffffffff1660e01b8152600401602060405180830381865afa158015610160573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101849190612beb565b6001600160a01b031663dc8af5f66040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101e59190612beb565b90506000886001600160a01b0316637cbf6db26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610227573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061024b9190612c1f565b90506000896001600160a01b031663dffeadd06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561028d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b19190612beb565b6001600160a01b031663979d7e866040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102ee573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103129190612beb565b6001600160a01b0316635ab1bd536040518163ffffffff1660e01b8152600401600060405180830381865afa15801561034f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103779190810190612d3f565b90506103878382600001516119cf565b8051516001600160401b038111156103a1576103a1612c3a565b6040519080825280602002602001820160405280156103ca578160200160208202803683370190505b508151519099506001600160401b038111156103e8576103e8612c3a565b604051908082528060200260200182016040528015610411578160200160208202803683370190505b508151519098506001600160401b0381111561042f5761042f612c3a565b604051908082528060200260200182016040528015610458578160200160208202803683370190505b508151519097506001600160401b0381111561047657610476612c3a565b60405190808252806020026020018201604052801561049f578160200160208202803683370190505b508151519096506001600160401b038111156104bd576104bd612c3a565b6040519080825280602002602001820160405280156104e6578160200160208202803683370190505b508151519095506001600160401b0381111561050457610504612c3a565b60405190808252806020026020018201604052801561052d578160200160208202803683370190505b50935060005b8151518110156109ea57815180518290811061055157610551612e30565b60200260200101518a828151811061056b5761056b612e30565b60200260200101906001600160a01b031690816001600160a01b03168152505060008b6001600160a01b031663a9f752e58c84815181106105ae576105ae612e30565b60200260200101516040518263ffffffff1660e01b81526004016105e191906001600160a01b0391909116815260200190565b602060405180830381865afa1580156105fe573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106229190612beb565b90506001600160a01b038116158015906106995750806001600160a01b031663faf7ba6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610675573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106999190612e46565b156106c1576106c18c8c84815181106106b4576106b4612e30565b6020026020010151611b92565b8a82815181106106d3576106d3612e30565b60209081029190910101516040516370a0823160e01b81526001600160a01b038e81166004830152909116906370a0823190602401602060405180830381865afa158015610725573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107499190612e68565b89838151811061075b5761075b612e30565b60200260200101818152505060008360200151838151811061077f5761077f612e30565b60200260200101516001600160a01b031663a035b1fe6040518163ffffffff1660e01b81526004016040805180830381865afa1580156107c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107e79190612e81565b509050806001600160c01b03166000036108025750506109da565b6108a88460200151848151811061081b5761081b612e30565b60200260200101516001600160a01b0316633cb5d3796040518163ffffffff1660e01b8152600401602060405180830381865afa158015610860573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108849190612eb4565b6108996001600160c01b038816846000611d1b565b6001600160c01b031690611daf565b8984815181106108ba576108ba612e30565b6020026020010181815250508883815181106108d8576108d8612e30565b60200260200101518a84815181106108f2576108f2612e30565b60200260200101511180156109ad575060006001600160a01b03168d6001600160a01b031663a9f752e58e868151811061092e5761092e612e30565b60200260200101516040518263ffffffff1660e01b815260040161096191906001600160a01b0391909116815260200190565b602060405180830381865afa15801561097e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109a29190612beb565b6001600160a01b0316145b156109d75760018b84815181106109c6576109c6612e30565b911515602092830291909101909101525b50505b6109e381612eed565b9050610533565b5060005b815151811015610aaf578151805182908110610a0c57610a0c612e30565b60209081029190910101516040516370a0823160e01b81526001600160a01b038681166004830152909116906370a0823190602401602060405180830381865afa158015610a5e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a829190612e68565b868281518110610a9457610a94612e30565b6020908102919091010152610aa881612eed565b90506109ee565b50826001600160a01b031663372500ab6040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610aeb57600080fd5b505af1925050508015610afc575060015b5060005b815151811015610be757858181518110610b1c57610b1c612e30565b602002602001015182600001518281518110610b3a57610b3a612e30565b60209081029190910101516040516370a0823160e01b81526001600160a01b038781166004830152909116906370a0823190602401602060405180830381865afa158015610b8c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bb09190612e68565b610bba9190612f06565b868281518110610bcc57610bcc612e30565b6020908102919091010152610be081612eed565b9050610b00565b5060005b815151811015610cac578151805182908110610c0957610c09612e30565b60209081029190910101516040516370a0823160e01b81526001600160a01b038d81166004830152909116906370a0823190602401602060405180830381865afa158015610c5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c7f9190612e68565b858281518110610c9157610c91612e30565b6020908102919091010152610ca581612eed565b9050610beb565b50896001600160a01b031663372500ab6040518163ffffffff1660e01b8152600401600060405180830381600087803b158015610ce857600080fd5b505af1925050508015610cf9575060015b5060005b815151811015610de457848181518110610d1957610d19612e30565b602002602001015182600001518281518110610d3757610d37612e30565b60209081029190910101516040516370a0823160e01b81526001600160a01b038e81166004830152909116906370a0823190602401602060405180830381865afa158015610d89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dad9190612e68565b610db79190612f06565b858281518110610dc957610dc9612e30565b6020908102919091010152610ddd81612eed565b9050610cfd565b5050505091939550919395565b6060816001600160401b03811115610e0b57610e0b612c3a565b604051908082528060200260200182016040528015610e3e57816020015b6060815260200190600190039081610e295790505b50905060005b82811015610ede57610eae30858584818110610e6257610e62612e30565b9050602002810190610e749190612f19565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dbd92505050565b828281518110610ec057610ec0612e30565b60200260200101819052508080610ed690612eed565b915050610e44565b505b92915050565b6000806000806000866001600160a01b031663dffeadd06040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f509190612beb565b6001600160a01b031663979d7e866040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f8d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb19190612beb565b6001600160a01b0316632510a1d76040518163ffffffff1660e01b8152600401600060405180830381865afa158015610fee573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526110169190810190612f5f565b90506000876001600160a01b031663c76aeb126040518163ffffffff1660e01b8152600401602060405180830381865afa158015611058573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061107c9190612f9b565b65ffffffffffff1611156111d85760005b81518110156111d6576000886001600160a01b031663a9f752e58484815181106110b9576110b9612e30565b60200260200101516040518263ffffffff1660e01b81526004016110ec91906001600160a01b0391909116815260200190565b602060405180830381865afa158015611109573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061112d9190612beb565b90506001600160a01b038116158015906111a45750806001600160a01b031663faf7ba6a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611180573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111a49190612e46565b156111c5576111bf898484815181106106b4576106b4612e30565b506111d6565b506111cf81612eed565b905061108d565b505b866001600160a01b031663c76aeb126040518163ffffffff1660e01b8152600401602060405180830381865afa158015611216573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061123a9190612f9b565b65ffffffffffff166000036113f2576112538787611de2565b60005b81518110156113f0576000886001600160a01b031663a9f752e584848151811061128257611282612e30565b60200260200101516040518263ffffffff1660e01b81526004016112b591906001600160a01b0391909116815260200190565b602060405180830381865afa1580156112d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112f69190612beb565b90506001600160a01b038116156113df5760019650806001600160a01b031663457100746040518163ffffffff1660e01b8152600401602060405180830381865afa158015611349573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061136d9190612beb565b9550806001600160a01b031663a6f2ae3a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113d19190612beb565b94506113dc81611f6d565b93505b506113e981612eed565b9050611256565b505b5092959194509250565b6000816001600160a01b031663dffeadd06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561143c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114609190612beb565b9050806001600160a01b031663dc8af5f66040518163ffffffff1660e01b8152600401602060405180830381865afa1580156114a0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114c49190612beb565b6001600160a01b031663372500ab6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156114fe57600080fd5b505af1158015611512573d6000803e3d6000fd5b50505050806001600160a01b03166341bf0c4e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611554573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115789190612beb565b6001600160a01b031663372500ab6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156115b257600080fd5b505af11580156115c6573d6000803e3d6000fd5b50505050806001600160a01b031663531367636040518163ffffffff1660e01b8152600401602060405180830381865afa158015611608573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061162c9190612beb565b6001600160a01b031663372500ab6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561166657600080fd5b505af115801561167a573d6000803e3d6000fd5b505050505050565b60005b858110156116cd576116bd888888848181106116a3576116a3612e30565b90506020020160208101906116b8919061287f565b611b92565b6116c681612eed565b9050611685565b506000876001600160a01b03166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa15801561170e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526117369190810190612fc3565b60008151811061174857611748612e30565b01602001516001600160f81b0319169050851580159061178c57506001600160f81b03198116601960f91b148061178c57506001600160f81b03198116603160f81b145b1561184857611846886001600160a01b03166319ca8d9d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117f69190612beb565b6040516001600160a01b03909116602482015260440160408051601f198184030181529190526020810180516001600160e01b0316639b4ec8e960e01b1790526001600160a01b038a1690612233565b505b600084900361185757506119c6565b611956886001600160a01b031663dffeadd06040518163ffffffff1660e01b8152600401602060405180830381865afa158015611898573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118bc9190612beb565b6001600160a01b031663dc8af5f66040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061191d9190612beb565b8686808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506119cf92505050565b6119c48886868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020808a0282810182019093528982529093508992508891829185019084908082843760009201919091525061227592505050565b505b50505050505050565b6000826001600160a01b03166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa158015611a0f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611a379190810190612fc3565b600081518110611a4957611a49612e30565b01602001516001600160f81b031916905060cd60f81b8101611ac657604051630e1510c360e21b81526001600160a01b03841690633854430c90611a91908590600401613056565b600060405180830381600087803b158015611aab57600080fd5b505af1925050508015611abc575060015b15611b8d57505050565b6001600160f81b03198116601960f91b1480611aef57506001600160f81b03198116603160f81b145b15611b85576000836001600160a01b0316600084604051602401611b139190613056565b60408051601f198184030181529181526020820180516001600160e01b0316631506e5fb60e31b17905251611b489190613069565b60006040518083038185875af1925050503d80600081146119c6576040519150601f19603f3d011682016040523d82523d6000602084013e6119c6565b611b8d61243b565b505050565b6000826001600160a01b03166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa158015611bd2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611bfa9190810190612fc3565b600081518110611c0c57611c0c612e30565b01602001516001600160f81b031916905060cd60f81b8101611c9a57604051630e0264d160e01b81526001600160a01b038381166004830152841690630e0264d1906024016020604051808303816000875af1158015611c70573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c949190612beb565b50505050565b6001600160f81b03198116601960f91b1480611cc357506001600160f81b03198116603160f81b145b15611b85576040516001600160a01b0383166024820152611c949060440160408051601f198184030181529190526020810180516001600160e01b0316630e0264d160e01b1790526001600160a01b03851690612233565b6000836001600160c01b0316600003611d3657506000611da8565b826001600160c01b0316600003611d5557506001600160c01b03611da8565b6000611d85611d756001600160c01b038716670de0b6b3a7640000613085565b856001600160c01b03168561247a565b90506001600160c01b038110611da5576001600160c01b03915050611da8565b90505b9392505050565b6000611da883836000612522565b6060611da883836040518060600160405280602781526020016132dc60279139612603565b6000826001600160a01b03166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa158015611e22573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611e4a9190810190612fc3565b600081518110611e5c57611e5c612e30565b01602001516001600160f81b031916905060cd60f81b8101611ea457604051633d2b851d60e21b81526001600160a01b0384169063f4ae147490611a919085906004016130d4565b6001600160f81b03198116601960f91b1480611ecd57506001600160f81b03198116603160f81b145b15611b855760408051600080825260208201909252906001600160a01b03851681611efb8460448101613056565b60408051601f198184030181529181526020820180516001600160e01b0316631506e5fb60e31b17905251611f309190613069565b60006040518083038185875af1925050503d80600081146119c4576040519150601f19603f3d011682016040523d82523d6000602084013e6119c4565b600080826001600160a01b031663c872da3c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611fae573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fd291906130e2565b6001811115611fe357611fe361309c565b0361211057610ee0826001600160a01b031663457100746040518163ffffffff1660e01b8152600401602060405180830381865afa158015612029573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061204d9190612beb565b6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561208a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120ae9190612eb4565b836001600160a01b0316633f407b846040518163ffffffff1660e01b8152600401602060405180830381865afa1580156120ec573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108999190612c1f565b6001826001600160a01b031663c872da3c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612150573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061217491906130e2565b60018111156121855761218561309c565b036121ec57816001600160a01b031663abdf2f9d6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156121c8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ee09190612e68565b60405162461bcd60e51b8152602060048201526012602482015271696e76616c6964207472616465207479706560701b60448201526064015b60405180910390fd5b919050565b6060611da883836040518060400160405280601e81526020017f416464726573733a206c6f772d6c6576656c2063616c6c206661696c656400008152506126d6565b6000836001600160a01b03166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa1580156122b5573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526122dd9190810190612fc3565b6000815181106122ef576122ef612e30565b01602001516001600160f81b031916905060cd60f81b81016123705760405163451f0b5360e11b81526001600160a01b03851690638a3e16a69061233990869086906004016130ff565b600060405180830381600087803b15801561235357600080fd5b505af1158015612367573d6000803e3d6000fd5b50505050611c94565b6001600160f81b03198116601960f91b148061239957506001600160f81b03198116603160f81b145b156124375760005b8351811015612431576124208482815181106123bf576123bf612e30565b60200260200101516040516024016123e691906001600160a01b0391909116815260200190565b60408051601f198184030181529190526020810180516001600160e01b0316639b4ec8e960e01b1790526001600160a01b03871690612233565b5061242a81612eed565b90506123a1565b50611c94565b611c945b60405162461bcd60e51b81526020600482015260146024820152733ab73932b1b7b3b734bd32b2103b32b939b4b7b760611b6044820152606401612225565b6000806124878486613173565b9050600083600281111561249d5761249d61309c565b036124a9579050611da8565b60018360028111156124bd576124bd61309c565b036124fc5760026124cf600186612f06565b6124d99190613173565b6124e38587613187565b11156124f757806124f381612eed565b9150505b611da5565b60006125088587613187565b1115611da5578061251881612eed565b9695505050505050565b6000836001600160c01b031660000361253d57506000611da8565b6029198360000b1361257657600282600281111561255d5761255d61309c565b1461256957600061256c565b60015b60ff169050611da8565b8260000b60601361259a5760405163f44398f560e01b815260040160405180910390fd5b6125a560128461319b565b925060006125b58460000b6126e5565b6125c090600a6132a0565b905060008460000b12156125e7576125e2856001600160c01b0316828561247a565b6125fa565b6125fa816001600160c01b038716613085565b95945050505050565b60606001600160a01b0384163b61266b5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401612225565b600080856001600160a01b0316856040516126869190613069565b600060405180830381855af49150503d80600081146126c1576040519150601f19603f3d011682016040523d82523d6000602084013e6126c6565b606091505b50915091506125188282866126fd565b6060611da58484600085612736565b60008082126126f45781610ee0565b610ee0826132ac565b6060831561270c575081611da8565b82511561271c5782518084602001fd5b8160405162461bcd60e51b815260040161222591906132c8565b6060824710156127975760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401612225565b6001600160a01b0385163b6127ee5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401612225565b600080866001600160a01b0316858760405161280a9190613069565b60006040518083038185875af1925050503d8060008114612847576040519150601f19603f3d011682016040523d82523d6000602084013e61284c565b606091505b509150915061285c8282866126fd565b979650505050505050565b6001600160a01b038116811461287c57600080fd5b50565b60006020828403121561289157600080fd5b8135611da881612867565b600081518084526020808501945080840160005b838110156128d55781516001600160a01b0316875295820195908201906001016128b0565b509495945050505050565b600081518084526020808501945080840160005b838110156128d5578151875295820195908201906001016128f4565b60c08152600061292360c083018961289c565b82810360208481019190915288518083528982019282019060005b8181101561295c57845115158352938301939183019160010161293e565b50508481036040860152612970818a6128e0565b92505050828103606084015261298681876128e0565b9050828103608084015261299a81866128e0565b905082810360a08401526129ae81856128e0565b9998505050505050505050565b60008083601f8401126129cd57600080fd5b5081356001600160401b038111156129e457600080fd5b6020830191508360208260051b85010111156129ff57600080fd5b9250929050565b60008060208385031215612a1957600080fd5b82356001600160401b03811115612a2f57600080fd5b612a3b858286016129bb565b90969095509350505050565b60005b83811015612a62578181015183820152602001612a4a565b50506000910152565b60008151808452612a83816020860160208601612a47565b601f01601f19169290920160200192915050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015612aec57603f19888603018452612ada858351612a6b565b94509285019290850190600101612abe565b5092979650505050505050565b6002811061287c57600080fd5b60008060408385031215612b1957600080fd5b8235612b2481612867565b91506020830135612b3481612af9565b809150509250929050565b60008060008060008060006080888a031215612b5a57600080fd5b8735612b6581612867565b965060208801356001600160401b0380821115612b8157600080fd5b612b8d8b838c016129bb565b909850965060408a0135915080821115612ba657600080fd5b612bb28b838c016129bb565b909650945060608a0135915080821115612bcb57600080fd5b50612bd88a828b016129bb565b989b979a50959850939692959293505050565b600060208284031215612bfd57600080fd5b8151611da881612867565b80516001600160c01b038116811461222e57600080fd5b600060208284031215612c3157600080fd5b611da882612c08565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715612c7257612c72612c3a565b60405290565b604051601f8201601f191681016001600160401b0381118282101715612ca057612ca0612c3a565b604052919050565b60006001600160401b03821115612cc157612cc1612c3a565b5060051b60200190565b600082601f830112612cdc57600080fd5b81516020612cf1612cec83612ca8565b612c78565b82815260059290921b84018101918181019086841115612d1057600080fd5b8286015b84811015612d34578051612d2781612867565b8352918301918301612d14565b509695505050505050565b60006020808385031215612d5257600080fd5b82516001600160401b0380821115612d6957600080fd5b9084019060408287031215612d7d57600080fd5b612d85612c50565b825182811115612d9457600080fd5b612da088828601612ccb565b8252508383015182811115612db457600080fd5b80840193505086601f840112612dc957600080fd5b82519150612dd9612cec83612ca8565b82815260059290921b83018401918481019088841115612df857600080fd5b938501935b83851015612e1f578451612e1081612867565b82529385019390850190612dfd565b948201949094529695505050505050565b634e487b7160e01b600052603260045260246000fd5b600060208284031215612e5857600080fd5b81518015158114611da857600080fd5b600060208284031215612e7a57600080fd5b5051919050565b60008060408385031215612e9457600080fd5b612e9d83612c08565b9150612eab60208401612c08565b90509250929050565b600060208284031215612ec657600080fd5b815160ff81168114611da857600080fd5b634e487b7160e01b600052601160045260246000fd5b600060018201612eff57612eff612ed7565b5060010190565b81810381811115610ee057610ee0612ed7565b6000808335601e19843603018112612f3057600080fd5b8301803591506001600160401b03821115612f4a57600080fd5b6020019150368190038213156129ff57600080fd5b600060208284031215612f7157600080fd5b81516001600160401b03811115612f8757600080fd5b612f9384828501612ccb565b949350505050565b600060208284031215612fad57600080fd5b815165ffffffffffff81168114611da857600080fd5b600060208284031215612fd557600080fd5b81516001600160401b0380821115612fec57600080fd5b818401915084601f83011261300057600080fd5b81518181111561301257613012612c3a565b613025601f8201601f1916602001612c78565b915080825285602082850101111561303c57600080fd5b61304d816020840160208601612a47565b50949350505050565b602081526000611da8602083018461289c565b6000825161307b818460208701612a47565b9190910192915050565b8082028115828204841417610ee057610ee0612ed7565b634e487b7160e01b600052602160045260246000fd5b600281106130d057634e487b7160e01b600052602160045260246000fd5b9052565b60208101610ee082846130b2565b6000602082840312156130f457600080fd5b8151611da881612af9565b604081526000613112604083018561289c565b82810360208481019190915284518083528582019282019060005b81811015613150576131408386516130b2565b938301939183019160010161312d565b5090979650505050505050565b634e487b7160e01b600052601260045260246000fd5b6000826131825761318261315d565b500490565b6000826131965761319661315d565b500690565b600082810b9082900b03607f198112607f82131715610ee057610ee0612ed7565b600181815b808511156131f75781600019048211156131dd576131dd612ed7565b808516156131ea57918102915b93841c93908002906131c1565b509250929050565b60008261320e57506001610ee0565b8161321b57506000610ee0565b8160018114613231576002811461323b57613257565b6001915050610ee0565b60ff84111561324c5761324c612ed7565b50506001821b610ee0565b5060208310610133831016604e8410600b841016171561327a575081810a610ee0565b61328483836131bc565b806000190482111561329857613298612ed7565b029392505050565b6000611da883836131ff565b6000600160ff1b82016132c1576132c1612ed7565b5060000390565b602081526000611da86020830184612a6b56fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212209135d485df692565020e393fd4723466f778de8036c8679ab09f9c4676f17ce164736f6c63430008130033
Verified Source Code Full Match
Compiler: v0.8.19+commit.7dd6d404
EVM: paris
Optimization: Yes (200 runs)
Fixed.sol 678 lines
// SPDX-License-Identifier: BlueOak-1.0.0 // solhint-disable func-name-mixedcase func-visibility // slither-disable-start divide-before-multiply pragma solidity ^0.8.19; /// @title FixedPoint, a fixed-point arithmetic library defining the custom type uint192 /// @author Matt Elder <[email protected]> and the Reserve Team <https://reserve.org> /** The logical type `uint192 ` is a 192 bit value, representing an 18-decimal Fixed-point fractional value. This is what's described in the Solidity documentation as "fixed192x18" -- a value represented by 192 bits, that makes 18 digits available to the right of the decimal point. The range of values that uint192 can represent is about [-1.7e20, 1.7e20]. Unless a function explicitly says otherwise, it will fail on overflow. To be clear, the following should hold: toFix(0) == 0 toFix(1) == 1e18 */ // Analysis notes: // Every function should revert iff its result is out of bounds. // Unless otherwise noted, when a rounding mode is given, that mode is applied to // a single division that may happen as the last step in the computation. // Unless otherwise noted, when a rounding mode is *not* given but is needed, it's FLOOR. // For each, we comment: // - @return is the value expressed in "value space", where uint192(1e18) "is" 1.0 // - as-ints: is the value expressed in "implementation space", where uint192(1e18) "is" 1e18 // The "@return" expression is suitable for actually using the library // The "as-ints" expression is suitable for testing // A uint value passed to this library was out of bounds for uint192 operations error UIntOutOfBounds(); bytes32 constant UIntOutofBoundsHash = keccak256(abi.encodeWithSignature("UIntOutOfBounds()")); // Used by P1 implementation for easier casting uint256 constant FIX_ONE_256 = 1e18; uint8 constant FIX_DECIMALS = 18; // If a particular uint192 is represented by the uint192 n, then the uint192 represents the // value n/FIX_SCALE. uint64 constant FIX_SCALE = 1e18; // FIX_SCALE Squared: uint128 constant FIX_SCALE_SQ = 1e36; // The largest integer that can be converted to uint192 . // This is a bit bigger than 3.1e39 uint192 constant FIX_MAX_INT = type(uint192).max / FIX_SCALE; uint192 constant FIX_ZERO = 0; // The uint192 representation of zero. uint192 constant FIX_ONE = FIX_SCALE; // The uint192 representation of one. uint192 constant FIX_MAX = type(uint192).max; // The largest uint192. (Not an integer!) uint192 constant FIX_MIN = 0; // The smallest uint192. /// An enum that describes a rounding approach for converting to ints enum RoundingMode { FLOOR, // Round towards zero ROUND, // Round to the nearest int CEIL // Round away from zero } RoundingMode constant FLOOR = RoundingMode.FLOOR; RoundingMode constant ROUND = RoundingMode.ROUND; RoundingMode constant CEIL = RoundingMode.CEIL; /* @dev Solidity 0.8.x only allows you to change one of type or size per type conversion. Thus, all the tedious-looking double conversions like uint256(uint256 (foo)) See: https://docs.soliditylang.org/en/v0.8.17/080-breaking-changes.html#new-restrictions */ /// Explicitly convert a uint256 to a uint192. Revert if the input is out of bounds. function _safeWrap(uint256 x) pure returns (uint192) { if (FIX_MAX < x) revert UIntOutOfBounds(); return uint192(x); } /// Convert a uint to its Fix representation. /// @return x // as-ints: x * 1e18 function toFix(uint256 x) pure returns (uint192) { return _safeWrap(x * FIX_SCALE); } /// Convert a uint to its fixed-point representation, and left-shift its value `shiftLeft` /// decimal digits. /// @return x * 10**shiftLeft // as-ints: x * 10**(shiftLeft + 18) function shiftl_toFix(uint256 x, int8 shiftLeft) pure returns (uint192) { return shiftl_toFix(x, shiftLeft, FLOOR); } /// @return x * 10**shiftLeft // as-ints: x * 10**(shiftLeft + 18) function shiftl_toFix( uint256 x, int8 shiftLeft, RoundingMode rounding ) pure returns (uint192) { // conditions for avoiding overflow if (x == 0) return 0; if (shiftLeft <= -96) return (rounding == CEIL ? 1 : 0); // 0 < uint.max / 10**77 < 0.5 if (40 <= shiftLeft) revert UIntOutOfBounds(); // 10**56 < FIX_MAX < 10**57 shiftLeft += 18; uint256 coeff = 10**abs(shiftLeft); uint256 shifted = (shiftLeft >= 0) ? x * coeff : _divrnd(x, coeff, rounding); return _safeWrap(shifted); } /// Divide a uint by a uint192, yielding a uint192 /// This may also fail if the result is MIN_uint192! not fixing this for optimization's sake. /// @return x / y // as-ints: x * 1e36 / y function divFix(uint256 x, uint192 y) pure returns (uint192) { // If we didn't have to worry about overflow, we'd just do `return x * 1e36 / _y` // If it's safe to do this operation the easy way, do it: if (x < uint256(type(uint256).max / FIX_SCALE_SQ)) { return _safeWrap(uint256(x * FIX_SCALE_SQ) / y); } else { return _safeWrap(mulDiv256(x, FIX_SCALE_SQ, y)); } } /// Divide a uint by a uint, yielding a uint192 /// @return x / y // as-ints: x * 1e18 / y function divuu(uint256 x, uint256 y) pure returns (uint192) { return _safeWrap(mulDiv256(FIX_SCALE, x, y)); } /// @return min(x,y) // as-ints: min(x,y) function fixMin(uint192 x, uint192 y) pure returns (uint192) { return x < y ? x : y; } /// @return max(x,y) // as-ints: max(x,y) function fixMax(uint192 x, uint192 y) pure returns (uint192) { return x > y ? x : y; } /// @return absoluteValue(x,y) // as-ints: absoluteValue(x,y) function abs(int256 x) pure returns (uint256) { return x < 0 ? uint256(-x) : uint256(x); } /// Divide two uints, returning a uint, using rounding mode `rounding`. /// @return numerator / divisor // as-ints: numerator / divisor function _divrnd( uint256 numerator, uint256 divisor, RoundingMode rounding ) pure returns (uint256) { uint256 result = numerator / divisor; if (rounding == FLOOR) return result; if (rounding == ROUND) { if (numerator % divisor > (divisor - 1) / 2) { result++; } } else { if (numerator % divisor > 0) { result++; } } return result; } library FixLib { /// Again, all arithmetic functions fail if and only if the result is out of bounds. /// Convert this fixed-point value to a uint. Round towards zero if needed. /// @return x // as-ints: x / 1e18 function toUint(uint192 x) internal pure returns (uint136) { return toUint(x, FLOOR); } /// Convert this uint192 to a uint /// @return x // as-ints: x / 1e18 with rounding function toUint(uint192 x, RoundingMode rounding) internal pure returns (uint136) { return uint136(_divrnd(uint256(x), FIX_SCALE, rounding)); } /// Return the uint192 shifted to the left by `decimal` digits /// (Similar to a bitshift but in base 10) /// @return x * 10**decimals // as-ints: x * 10**decimals function shiftl(uint192 x, int8 decimals) internal pure returns (uint192) { return shiftl(x, decimals, FLOOR); } /// Return the uint192 shifted to the left by `decimal` digits /// (Similar to a bitshift but in base 10) /// @return x * 10**decimals // as-ints: x * 10**decimals function shiftl( uint192 x, int8 decimals, RoundingMode rounding ) internal pure returns (uint192) { // Handle overflow cases if (x == 0) return 0; if (decimals <= -59) return (rounding == CEIL ? 1 : 0); // 59, because 1e58 > 2**192 if (58 <= decimals) revert UIntOutOfBounds(); // 58, because x * 1e58 > 2 ** 192 if x != 0 uint256 coeff = uint256(10**abs(decimals)); return _safeWrap(decimals >= 0 ? x * coeff : _divrnd(x, coeff, rounding)); } /// Add a uint192 to this uint192 /// @return x + y // as-ints: x + y function plus(uint192 x, uint192 y) internal pure returns (uint192) { return x + y; } /// Add a uint to this uint192 /// @return x + y // as-ints: x + y*1e18 function plusu(uint192 x, uint256 y) internal pure returns (uint192) { return _safeWrap(x + y * FIX_SCALE); } /// Subtract a uint192 from this uint192 /// @return x - y // as-ints: x - y function minus(uint192 x, uint192 y) internal pure returns (uint192) { return x - y; } /// Subtract a uint from this uint192 /// @return x - y // as-ints: x - y*1e18 function minusu(uint192 x, uint256 y) internal pure returns (uint192) { return _safeWrap(uint256(x) - uint256(y * FIX_SCALE)); } /// Multiply this uint192 by a uint192 /// Round truncated values to the nearest available value. 5e-19 rounds away from zero. /// @return x * y // as-ints: x * y/1e18 [division using ROUND, not FLOOR] function mul(uint192 x, uint192 y) internal pure returns (uint192) { return mul(x, y, ROUND); } /// Multiply this uint192 by a uint192 /// @return x * y // as-ints: x * y/1e18 function mul( uint192 x, uint192 y, RoundingMode rounding ) internal pure returns (uint192) { return _safeWrap(_divrnd(uint256(x) * uint256(y), FIX_SCALE, rounding)); } /// Multiply this uint192 by a uint /// @return x * y // as-ints: x * y function mulu(uint192 x, uint256 y) internal pure returns (uint192) { return _safeWrap(x * y); } /// Divide this uint192 by a uint192 /// @return x / y // as-ints: x * 1e18 / y function div(uint192 x, uint192 y) internal pure returns (uint192) { return div(x, y, FLOOR); } /// Divide this uint192 by a uint192 /// @return x / y // as-ints: x * 1e18 / y function div( uint192 x, uint192 y, RoundingMode rounding ) internal pure returns (uint192) { // Multiply-in FIX_SCALE before dividing by y to preserve precision. return _safeWrap(_divrnd(uint256(x) * FIX_SCALE, y, rounding)); } /// Divide this uint192 by a uint /// @return x / y // as-ints: x / y function divu(uint192 x, uint256 y) internal pure returns (uint192) { return divu(x, y, FLOOR); } /// Divide this uint192 by a uint /// @return x / y // as-ints: x / y function divu( uint192 x, uint256 y, RoundingMode rounding ) internal pure returns (uint192) { return _safeWrap(_divrnd(x, y, rounding)); } uint64 constant FIX_HALF = uint64(FIX_SCALE) / 2; /// Raise this uint192 to a nonnegative integer power. Requires that x_ <= FIX_ONE /// Gas cost is O(lg(y)), precision is +- 1e-18. /// @return x_ ** y // as-ints: x_ ** y / 1e18**(y-1) <- technically correct for y = 0. :D function powu(uint192 x_, uint48 y) internal pure returns (uint192) { require(x_ <= FIX_ONE); if (y == 1) return x_; if (x_ == FIX_ONE || y == 0) return FIX_ONE; uint256 x = uint256(x_) * FIX_SCALE; // x is D36 uint256 result = FIX_SCALE_SQ; // result is D36 while (true) { if (y & 1 == 1) result = (result * x + FIX_SCALE_SQ / 2) / FIX_SCALE_SQ; if (y <= 1) break; y = (y >> 1); x = (x * x + FIX_SCALE_SQ / 2) / FIX_SCALE_SQ; } return _safeWrap(result / FIX_SCALE); } /// Comparison operators... function lt(uint192 x, uint192 y) internal pure returns (bool) { return x < y; } function lte(uint192 x, uint192 y) internal pure returns (bool) { return x <= y; } function gt(uint192 x, uint192 y) internal pure returns (bool) { return x > y; } function gte(uint192 x, uint192 y) internal pure returns (bool) { return x >= y; } function eq(uint192 x, uint192 y) internal pure returns (bool) { return x == y; } function neq(uint192 x, uint192 y) internal pure returns (bool) { return x != y; } /// Return whether or not this uint192 is less than epsilon away from y. /// @return |x - y| < epsilon // as-ints: |x - y| < epsilon function near( uint192 x, uint192 y, uint192 epsilon ) internal pure returns (bool) { uint192 diff = x <= y ? y - x : x - y; return diff < epsilon; } // ================ Chained Operations ================ // The operation foo_bar() always means: // Do foo() followed by bar(), and overflow only if the _end_ result doesn't fit in an uint192 /// Shift this uint192 left by `decimals` digits, and convert to a uint /// @return x * 10**decimals // as-ints: x * 10**(decimals - 18) function shiftl_toUint(uint192 x, int8 decimals) internal pure returns (uint256) { return shiftl_toUint(x, decimals, FLOOR); } /// Shift this uint192 left by `decimals` digits, and convert to a uint. /// @return x * 10**decimals // as-ints: x * 10**(decimals - 18) function shiftl_toUint( uint192 x, int8 decimals, RoundingMode rounding ) internal pure returns (uint256) { // Handle overflow cases if (x == 0) return 0; // always computable, no matter what decimals is if (decimals <= -42) return (rounding == CEIL ? 1 : 0); if (96 <= decimals) revert UIntOutOfBounds(); decimals -= 18; // shift so that toUint happens at the same time. uint256 coeff = uint256(10**abs(decimals)); return decimals >= 0 ? uint256(x * coeff) : uint256(_divrnd(x, coeff, rounding)); } /// Multiply this uint192 by a uint, and output the result as a uint /// @return x * y // as-ints: x * y / 1e18 function mulu_toUint(uint192 x, uint256 y) internal pure returns (uint256) { return mulDiv256(uint256(x), y, FIX_SCALE); } /// Multiply this uint192 by a uint, and output the result as a uint /// @return x * y // as-ints: x * y / 1e18 function mulu_toUint( uint192 x, uint256 y, RoundingMode rounding ) internal pure returns (uint256) { return mulDiv256(uint256(x), y, FIX_SCALE, rounding); } /// Multiply this uint192 by a uint192 and output the result as a uint /// @return x * y // as-ints: x * y / 1e36 function mul_toUint(uint192 x, uint192 y) internal pure returns (uint256) { return mulDiv256(uint256(x), uint256(y), FIX_SCALE_SQ); } /// Multiply this uint192 by a uint192 and output the result as a uint /// @return x * y // as-ints: x * y / 1e36 function mul_toUint( uint192 x, uint192 y, RoundingMode rounding ) internal pure returns (uint256) { return mulDiv256(uint256(x), uint256(y), FIX_SCALE_SQ, rounding); } /// Compute x * y / z avoiding intermediate overflow /// @dev Only use if you need to avoid overflow; costlier than x * y / z /// @return x * y / z // as-ints: x * y / z function muluDivu( uint192 x, uint256 y, uint256 z ) internal pure returns (uint192) { return muluDivu(x, y, z, FLOOR); } /// Compute x * y / z, avoiding intermediate overflow /// @dev Only use if you need to avoid overflow; costlier than x * y / z /// @return x * y / z // as-ints: x * y / z function muluDivu( uint192 x, uint256 y, uint256 z, RoundingMode rounding ) internal pure returns (uint192) { return _safeWrap(mulDiv256(x, y, z, rounding)); } /// Compute x * y / z on Fixes, avoiding intermediate overflow /// @dev Only use if you need to avoid overflow; costlier than x * y / z /// @return x * y / z // as-ints: x * y / z function mulDiv( uint192 x, uint192 y, uint192 z ) internal pure returns (uint192) { return mulDiv(x, y, z, FLOOR); } /// Compute x * y / z on Fixes, avoiding intermediate overflow /// @dev Only use if you need to avoid overflow; costlier than x * y / z /// @return x * y / z // as-ints: x * y / z function mulDiv( uint192 x, uint192 y, uint192 z, RoundingMode rounding ) internal pure returns (uint192) { return _safeWrap(mulDiv256(x, y, z, rounding)); } // === safe*() === /// Multiply two fixes, rounding up to FIX_MAX and down to 0 /// @param a First param to multiply /// @param b Second param to multiply function safeMul( uint192 a, uint192 b, RoundingMode rounding ) internal pure returns (uint192) { // untestable: // a will never = 0 here because of the check in _price() if (a == 0 || b == 0) return 0; // untestable: // a = FIX_MAX iff b = 0 if (a == FIX_MAX || b == FIX_MAX) return FIX_MAX; // return FIX_MAX instead of throwing overflow errors. unchecked { // p and mul *are* Fix values, so have 18 decimals (D18) uint256 rawDelta = uint256(b) * a; // {D36} = {D18} * {D18} // if we overflowed, then return FIX_MAX if (rawDelta / b != a) return FIX_MAX; uint256 shiftDelta = rawDelta; // add in rounding if (rounding == RoundingMode.ROUND) shiftDelta += (FIX_ONE / 2); else if (rounding == RoundingMode.CEIL) shiftDelta += FIX_ONE - 1; // untestable (here there be dragons): // (below explanation is for the ROUND case, but it extends to the FLOOR/CEIL too) // A) shiftDelta = rawDelta + (FIX_ONE / 2) // shiftDelta overflows if: // B) shiftDelta = MAX_UINT256 - FIX_ONE/2 + 1 // rawDelta + (FIX_ONE/2) = MAX_UINT256 - FIX_ONE/2 + 1 // b * a = MAX_UINT256 - FIX_ONE + 1 // therefore shiftDelta overflows if: // C) b = (MAX_UINT256 - FIX_ONE + 1) / a // MAX_UINT256 ~= 1e77 , FIX_MAX ~= 6e57 (6e20 difference in magnitude) // a <= 1e21 (MAX_TARGET_AMT) // a must be between 1e19 & 1e20 in order for b in (C) to be uint192, // but a would have to be < 1e18 in order for (A) to overflow if (shiftDelta < rawDelta) return FIX_MAX; // return FIX_MAX if return result would truncate if (shiftDelta / FIX_ONE > FIX_MAX) return FIX_MAX; // return _div(rawDelta, FIX_ONE, rounding) return uint192(shiftDelta / FIX_ONE); // {D18} = {D36} / {D18} } } /// Divide two fixes, rounding up to FIX_MAX and down to 0 /// @param a Numerator /// @param b Denominator function safeDiv( uint192 a, uint192 b, RoundingMode rounding ) internal pure returns (uint192) { if (a == 0) return 0; if (b == 0) return FIX_MAX; uint256 raw = _divrnd(FIX_ONE_256 * a, uint256(b), rounding); if (raw >= FIX_MAX) return FIX_MAX; return uint192(raw); // don't need _safeWrap } /// Multiplies two fixes and divide by a third /// @param a First to multiply /// @param b Second to multiply /// @param c Denominator function safeMulDiv( uint192 a, uint192 b, uint192 c, RoundingMode rounding ) internal pure returns (uint192 result) { if (a == 0 || b == 0) return 0; if (a == FIX_MAX || b == FIX_MAX || c == 0) return FIX_MAX; uint256 result_256; unchecked { (uint256 hi, uint256 lo) = fullMul(a, b); if (hi >= c) return FIX_MAX; uint256 mm = mulmod(a, b, c); if (mm > lo) hi -= 1; lo -= mm; uint256 pow2 = c & (0 - c); uint256 c_256 = uint256(c); // Warning: Should not access c below this line c_256 /= pow2; lo /= pow2; lo += hi * ((0 - pow2) / pow2 + 1); uint256 r = 1; r *= 2 - c_256 * r; r *= 2 - c_256 * r; r *= 2 - c_256 * r; r *= 2 - c_256 * r; r *= 2 - c_256 * r; r *= 2 - c_256 * r; r *= 2 - c_256 * r; r *= 2 - c_256 * r; result_256 = lo * r; // Apply rounding if (rounding == CEIL) { if (mm > 0) result_256 += 1; } else if (rounding == ROUND) { if (mm > ((c_256 - 1) / 2)) result_256 += 1; } } if (result_256 >= FIX_MAX) return FIX_MAX; return uint192(result_256); } } // ================ a couple pure-uint helpers================ // as-ints comments are omitted here, because they're the same as @return statements, because // these are all pure uint functions /// Return (x*y/z), avoiding intermediate overflow. // Adapted from sources: // https://medium.com/coinmonks/4db014e080b1, https://medium.com/wicketh/afa55870a65 // and quite a few of the other excellent "Mathemagic" posts from https://medium.com/wicketh /// @dev Only use if you need to avoid overflow; costlier than x * y / z /// @return result x * y / z function mulDiv256( uint256 x, uint256 y, uint256 z ) pure returns (uint256 result) { unchecked { (uint256 hi, uint256 lo) = fullMul(x, y); if (hi >= z) revert UIntOutOfBounds(); uint256 mm = mulmod(x, y, z); if (mm > lo) hi -= 1; lo -= mm; uint256 pow2 = z & (0 - z); z /= pow2; lo /= pow2; lo += hi * ((0 - pow2) / pow2 + 1); uint256 r = 1; r *= 2 - z * r; r *= 2 - z * r; r *= 2 - z * r; r *= 2 - z * r; r *= 2 - z * r; r *= 2 - z * r; r *= 2 - z * r; r *= 2 - z * r; result = lo * r; } } /// Return (x*y/z), avoiding intermediate overflow. /// @dev Only use if you need to avoid overflow; costlier than x * y / z /// @return x * y / z function mulDiv256( uint256 x, uint256 y, uint256 z, RoundingMode rounding ) pure returns (uint256) { uint256 result = mulDiv256(x, y, z); if (rounding == FLOOR) return result; uint256 mm = mulmod(x, y, z); if (rounding == CEIL) { if (mm > 0) result += 1; } else { if (mm > ((z - 1) / 2)) result += 1; // z should be z-1 } return result; } /// Return (x*y) as a "virtual uint512" (lo, hi), representing (hi*2**256 + lo) /// Adapted from sources: /// https://medium.com/wicketh/27650fec525d, https://medium.com/coinmonks/4db014e080b1 /// @dev Intended to be internal to this library /// @return hi (hi, lo) satisfies hi*(2**256) + lo == x * y /// @return lo (paired with `hi`) function fullMul(uint256 x, uint256 y) pure returns (uint256 hi, uint256 lo) { unchecked { uint256 mm = mulmod(x, y, uint256(0) - uint256(1)); lo = x * y; hi = mm - lo; if (mm < lo) hi -= 1; } } // slither-disable-end divide-before-multiply
IMain.sol 195 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IAssetRegistry.sol";
import "./IBasketHandler.sol";
import "./IBackingManager.sol";
import "./IBroker.sol";
import "./IGnosis.sol";
import "./IFurnace.sol";
import "./IDistributor.sol";
import "./IRToken.sol";
import "./IRevenueTrader.sol";
import "./IStRSR.sol";
import "./ITrading.sol";
import "./IVersioned.sol";
// === Auth roles ===
bytes32 constant OWNER = bytes32(bytes("OWNER"));
bytes32 constant SHORT_FREEZER = bytes32(bytes("SHORT_FREEZER"));
bytes32 constant LONG_FREEZER = bytes32(bytes("LONG_FREEZER"));
bytes32 constant PAUSER = bytes32(bytes("PAUSER"));
/**
* Main is a central hub that maintains a list of Component contracts.
*
* Components:
* - perform a specific function
* - defer auth to Main
* - usually (but not always) contain sizeable state that require a proxy
*/
struct Components {
// Definitely need proxy
IRToken rToken;
IStRSR stRSR;
IAssetRegistry assetRegistry;
IBasketHandler basketHandler;
IBackingManager backingManager;
IDistributor distributor;
IFurnace furnace;
IBroker broker;
IRevenueTrader rsrTrader;
IRevenueTrader rTokenTrader;
}
interface IAuth is IAccessControlUpgradeable {
/// Emitted when `unfreezeAt` is changed
/// @param oldVal The old value of `unfreezeAt`
/// @param newVal The new value of `unfreezeAt`
event UnfreezeAtSet(uint48 oldVal, uint48 newVal);
/// Emitted when the short freeze duration governance param is changed
/// @param oldDuration The old short freeze duration
/// @param newDuration The new short freeze duration
event ShortFreezeDurationSet(uint48 oldDuration, uint48 newDuration);
/// Emitted when the long freeze duration governance param is changed
/// @param oldDuration The old long freeze duration
/// @param newDuration The new long freeze duration
event LongFreezeDurationSet(uint48 oldDuration, uint48 newDuration);
/// Emitted when the system is paused or unpaused for trading
/// @param oldVal The old value of `tradingPaused`
/// @param newVal The new value of `tradingPaused`
event TradingPausedSet(bool oldVal, bool newVal);
/// Emitted when the system is paused or unpaused for issuance
/// @param oldVal The old value of `issuancePaused`
/// @param newVal The new value of `issuancePaused`
event IssuancePausedSet(bool oldVal, bool newVal);
/**
* Trading Paused: Disable everything except for OWNER actions, RToken.issue, RToken.redeem,
* StRSR.stake, and StRSR.payoutRewards
* Issuance Paused: Disable RToken.issue
* Frozen: Disable everything except for OWNER actions + StRSR.stake (for governance)
*/
function tradingPausedOrFrozen() external view returns (bool);
function issuancePausedOrFrozen() external view returns (bool);
function frozen() external view returns (bool);
function shortFreeze() external view returns (uint48);
function longFreeze() external view returns (uint48);
// ====
// onlyRole(OWNER)
function freezeForever() external;
// onlyRole(SHORT_FREEZER)
function freezeShort() external;
// onlyRole(LONG_FREEZER)
function freezeLong() external;
// onlyRole(OWNER)
function unfreeze() external;
function pauseTrading() external;
function unpauseTrading() external;
function pauseIssuance() external;
function unpauseIssuance() external;
}
interface IComponentRegistry {
// === Component setters/getters ===
event RTokenSet(IRToken indexed oldVal, IRToken indexed newVal);
function rToken() external view returns (IRToken);
event StRSRSet(IStRSR oldVal, IStRSR newVal);
function stRSR() external view returns (IStRSR);
event AssetRegistrySet(IAssetRegistry oldVal, IAssetRegistry newVal);
function assetRegistry() external view returns (IAssetRegistry);
event BasketHandlerSet(IBasketHandler oldVal, IBasketHandler newVal);
function basketHandler() external view returns (IBasketHandler);
event BackingManagerSet(IBackingManager oldVal, IBackingManager newVal);
function backingManager() external view returns (IBackingManager);
event DistributorSet(IDistributor oldVal, IDistributor newVal);
function distributor() external view returns (IDistributor);
event RSRTraderSet(IRevenueTrader oldVal, IRevenueTrader newVal);
function rsrTrader() external view returns (IRevenueTrader);
event RTokenTraderSet(IRevenueTrader oldVal, IRevenueTrader newVal);
function rTokenTrader() external view returns (IRevenueTrader);
event FurnaceSet(IFurnace oldVal, IFurnace newVal);
function furnace() external view returns (IFurnace);
event BrokerSet(IBroker oldVal, IBroker newVal);
function broker() external view returns (IBroker);
}
/**
* @title IMain
* @notice The central hub for the entire system. Maintains components and an owner singleton role
*/
interface IMain is IVersioned, IAuth, IComponentRegistry {
function poke() external; // not used in p1
// === Initialization ===
event MainInitialized();
function init(
Components memory components,
IERC20 rsr_,
uint48 shortFreeze_,
uint48 longFreeze_
) external;
function rsr() external view returns (IERC20);
}
interface TestIMain is IMain {
/// @custom:governance
function setShortFreeze(uint48) external;
/// @custom:governance
function setLongFreeze(uint48) external;
function shortFreeze() external view returns (uint48);
function longFreeze() external view returns (uint48);
function longFreezes(address account) external view returns (uint256);
function tradingPaused() external view returns (bool);
function issuancePaused() external view returns (bool);
}
IAsset.sol 145 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../libraries/Fixed.sol";
import "./IMain.sol";
import "./IRewardable.sol";
// Not used directly in the IAsset interface, but used by many consumers to save stack space
struct Price {
uint192 low; // {UoA/tok}
uint192 high; // {UoA/tok}
}
/**
* @title IAsset
* @notice Supertype. Any token that interacts with our system must be wrapped in an asset,
* whether it is used as RToken backing or not. Any token that can report a price in the UoA
* is eligible to be an asset.
*/
interface IAsset is IRewardable {
/// Refresh saved price
/// The Reserve protocol calls this at least once per transaction, before relying on
/// the Asset's other functions.
/// @dev Called immediately after deployment, before use
function refresh() external;
/// Should not revert
/// low should be nonzero if the asset could be worth selling
/// @return low {UoA/tok} The lower end of the price estimate
/// @return high {UoA/tok} The upper end of the price estimate
function price() external view returns (uint192 low, uint192 high);
/// Should not revert
/// lotLow should be nonzero when the asset might be worth selling
/// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility
/// @return lotLow {UoA/tok} The lower end of the lot price estimate
/// @return lotHigh {UoA/tok} The upper end of the lot price estimate
function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh);
/// @return {tok} The balance of the ERC20 in whole tokens
function bal(address account) external view returns (uint192);
/// @return The ERC20 contract of the token with decimals() available
function erc20() external view returns (IERC20Metadata);
/// @return The number of decimals in the ERC20; just for gas optimization
function erc20Decimals() external view returns (uint8);
/// @return If the asset is an instance of ICollateral or not
function isCollateral() external view returns (bool);
/// @return {UoA} The max trade volume, in UoA
function maxTradeVolume() external view returns (uint192);
/// @return {s} The timestamp of the last refresh() that saved prices
function lastSave() external view returns (uint48);
}
// Used only in Testing. Strictly speaking an Asset does not need to adhere to this interface
interface TestIAsset is IAsset {
/// @return The address of the chainlink feed
function chainlinkFeed() external view returns (AggregatorV3Interface);
/// {1} The max % deviation allowed by the oracle
function oracleError() external view returns (uint192);
/// @return {s} Seconds that an oracle value is considered valid
function oracleTimeout() external view returns (uint48);
/// @return {s} The maximum of all oracle timeouts on the plugin
function maxOracleTimeout() external view returns (uint48);
/// @return {s} Seconds that the price() should decay over, after stale price
function priceTimeout() external view returns (uint48);
/// @return {UoA/tok} The last saved low price
function savedLowPrice() external view returns (uint192);
/// @return {UoA/tok} The last saved high price
function savedHighPrice() external view returns (uint192);
}
/// CollateralStatus must obey a linear ordering. That is:
/// - being DISABLED is worse than being IFFY, or SOUND
/// - being IFFY is worse than being SOUND.
enum CollateralStatus {
SOUND,
IFFY, // When a peg is not holding or a chainlink feed is stale
DISABLED // When the collateral has completely defaulted
}
/// Upgrade-safe maximum operator for CollateralStatus
library CollateralStatusComparator {
/// @return Whether a is worse than b
function worseThan(CollateralStatus a, CollateralStatus b) internal pure returns (bool) {
return uint256(a) > uint256(b);
}
}
/**
* @title ICollateral
* @notice A subtype of Asset that consists of the tokens eligible to back the RToken.
*/
interface ICollateral is IAsset {
/// Emitted whenever the collateral status is changed
/// @param newStatus The old CollateralStatus
/// @param newStatus The updated CollateralStatus
event CollateralStatusChanged(
CollateralStatus indexed oldStatus,
CollateralStatus indexed newStatus
);
/// @dev refresh()
/// Refresh exchange rates and update default status.
/// VERY IMPORTANT: In any valid implemntation, status() MUST become DISABLED in refresh() if
/// refPerTok() has ever decreased since last call.
/// @return The canonical name of this collateral's target unit.
function targetName() external view returns (bytes32);
/// @return The status of this collateral asset. (Is it defaulting? Might it soon?)
function status() external view returns (CollateralStatus);
// ==== Exchange Rates ====
/// @return {ref/tok} Quantity of whole reference units per whole collateral tokens
function refPerTok() external view returns (uint192);
/// @return {target/ref} Quantity of whole target units per whole reference unit in the peg
function targetPerRef() external view returns (uint192);
}
// Used only in Testing. Strictly speaking a Collateral does not need to adhere to this interface
interface TestICollateral is TestIAsset, ICollateral {
/// @return The epoch timestamp when the collateral will default from IFFY to DISABLED
function whenDefault() external view returns (uint256);
/// @return The amount of time a collateral must be in IFFY status until being DISABLED
function delayUntilDefault() external view returns (uint48);
/// @return The underlying refPerTok, likely not included in all collaterals however.
function underlyingRefPerTok() external view returns (uint192);
}
IStRSR.sol 167 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
// solhint-disable-next-line max-line-length
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol";
import "../libraries/Fixed.sol";
import "./IComponent.sol";
import "./IMain.sol";
/**
* @title IStRSR
* @notice An ERC20 token representing shares of the RSR over-collateralization pool.
*
* StRSR permits the BackingManager to take RSR in times of need. In return, the BackingManager
* benefits the StRSR pool with RSR rewards purchased with a portion of its revenue.
*
* In the absence of collateral default or losses due to slippage, StRSR should have a
* monotonically increasing exchange rate with respect to RSR, meaning that over time
* StRSR is redeemable for more RSR. It is non-rebasing.
*/
interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, IComponent {
/// Emitted when RSR is staked
/// @param era The era at time of staking
/// @param staker The address of the staker
/// @param rsrAmount {qRSR} How much RSR was staked
/// @param stRSRAmount {qStRSR} How much stRSR was minted by this staking
event Staked(
uint256 indexed era,
address indexed staker,
uint256 rsrAmount,
uint256 stRSRAmount
);
/// Emitted when an unstaking is started
/// @param draftId The id of the draft.
/// @param draftEra The era of the draft.
/// @param staker The address of the unstaker
/// The triple (staker, draftEra, draftId) is a unique ID
/// @param rsrAmount {qRSR} How much RSR this unstaking will be worth, absent seizures
/// @param stRSRAmount {qStRSR} How much stRSR was burned by this unstaking
event UnstakingStarted(
uint256 indexed draftId,
uint256 indexed draftEra,
address indexed staker,
uint256 rsrAmount,
uint256 stRSRAmount,
uint256 availableAt
);
/// Emitted when RSR is unstaked
/// @param firstId The beginning of the range of draft IDs withdrawn in this transaction
/// @param endId The end of range of draft IDs withdrawn in this transaction
/// (ID i was withdrawn if firstId <= i < endId)
/// @param draftEra The era of the draft.
/// The triple (staker, draftEra, id) is a unique ID among drafts
/// @param staker The address of the unstaker
/// @param rsrAmount {qRSR} How much RSR this unstaking was worth
event UnstakingCompleted(
uint256 indexed firstId,
uint256 indexed endId,
uint256 draftEra,
address indexed staker,
uint256 rsrAmount
);
/// Emitted when RSR unstaking is cancelled
/// @param firstId The beginning of the range of draft IDs withdrawn in this transaction
/// @param endId The end of range of draft IDs withdrawn in this transaction
/// (ID i was withdrawn if firstId <= i < endId)
/// @param draftEra The era of the draft.
/// The triple (staker, draftEra, id) is a unique ID among drafts
/// @param staker The address of the unstaker
/// @param rsrAmount {qRSR} How much RSR this unstaking was worth
event UnstakingCancelled(
uint256 indexed firstId,
uint256 indexed endId,
uint256 draftEra,
address indexed staker,
uint256 rsrAmount
);
/// Emitted whenever the exchange rate changes
event ExchangeRateSet(uint192 oldVal, uint192 newVal);
/// Emitted whenever RSR are paids out
event RewardsPaid(uint256 rsrAmt);
/// Emitted if all the RSR in the staking pool is seized and all balances are reset to zero.
event AllBalancesReset(uint256 indexed newEra);
/// Emitted if all the RSR in the unstakin pool is seized, and all ongoing unstaking is voided.
event AllUnstakingReset(uint256 indexed newEra);
event UnstakingDelaySet(uint48 oldVal, uint48 newVal);
event RewardRatioSet(uint192 oldVal, uint192 newVal);
event WithdrawalLeakSet(uint192 oldVal, uint192 newVal);
// Initialization
function init(
IMain main_,
string memory name_,
string memory symbol_,
uint48 unstakingDelay_,
uint192 rewardRatio_,
uint192 withdrawalLeak_
) external;
/// Gather and payout rewards from rsrTrader
/// @custom:interaction
function payoutRewards() external;
/// Stakes an RSR `amount` on the corresponding RToken to earn yield and over-collateralized
/// the system
/// @param amount {qRSR}
/// @custom:interaction
function stake(uint256 amount) external;
/// Begins a delayed unstaking for `amount` stRSR
/// @param amount {qStRSR}
/// @custom:interaction
function unstake(uint256 amount) external;
/// Complete delayed unstaking for the account, up to (but not including!) `endId`
/// @custom:interaction
function withdraw(address account, uint256 endId) external;
/// Cancel unstaking for the account, up to (but not including!) `endId`
/// @custom:interaction
function cancelUnstake(uint256 endId) external;
/// Seize RSR, only callable by main.backingManager()
/// @custom:protected
function seizeRSR(uint256 amount) external;
/// Reset all stakes and advance era
/// @custom:governance
function resetStakes() external;
/// Return the maximum valid value of endId such that withdraw(endId) should immediately work
function endIdForWithdraw(address account) external view returns (uint256 endId);
/// @return {qRSR/qStRSR} The exchange rate between RSR and StRSR
function exchangeRate() external view returns (uint192);
}
interface TestIStRSR is IStRSR {
function rewardRatio() external view returns (uint192);
function setRewardRatio(uint192) external;
function unstakingDelay() external view returns (uint48);
function setUnstakingDelay(uint48) external;
function withdrawalLeak() external view returns (uint192);
function setWithdrawalLeak(uint192) external;
function increaseAllowance(address, uint256) external returns (bool);
function decreaseAllowance(address, uint256) external returns (bool);
/// @return {qStRSR/qRSR} The exchange rate between StRSR and RSR
function exchangeRate() external view returns (uint192);
}
ITrade.sol 43 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "./IBroker.sol";
enum TradeStatus {
NOT_STARTED, // before init()
OPEN, // after init() and before settle()
CLOSED, // after settle()
// === Intermediate-tx state ===
PENDING // during init() or settle() (reentrancy protection)
}
/**
* Simple generalized trading interface for all Trade contracts to obey
*
* Usage: if (canSettle()) settle()
*/
interface ITrade {
/// Complete the trade and transfer tokens back to the origin trader
/// @return soldAmt {qSellTok} The quantity of tokens sold
/// @return boughtAmt {qBuyTok} The quantity of tokens bought
function settle() external returns (uint256 soldAmt, uint256 boughtAmt);
function sell() external view returns (IERC20Metadata);
function buy() external view returns (IERC20Metadata);
/// @return {tok} The sell amount of the trade, in whole tokens
function sellAmount() external view returns (uint192);
/// @return The timestamp at which the trade is projected to become settle-able
function endTime() external view returns (uint48);
/// @return True if the trade can be settled
/// @dev Should be guaranteed to be true eventually as an invariant
function canSettle() external view returns (bool);
/// @return TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION
// solhint-disable-next-line func-name-mixedcase
function KIND() external view returns (TradeKind);
}
IBroker.sol 98 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "./IAsset.sol";
import "./IComponent.sol";
import "./IGnosis.sol";
import "./ITrade.sol";
enum TradeKind {
DUTCH_AUCTION,
BATCH_AUCTION
}
/// Cache of all prices for a pair to prevent re-lookup
struct TradePrices {
uint192 sellLow; // {UoA/sellTok} can be 0
uint192 sellHigh; // {UoA/sellTok} should not be 0
uint192 buyLow; // {UoA/buyTok} should not be 0
uint192 buyHigh; // {UoA/buyTok} should not be 0 or FIX_MAX
}
/// The data format that describes a request for trade with the Broker
struct TradeRequest {
IAsset sell;
IAsset buy;
uint256 sellAmount; // {qSellTok}
uint256 minBuyAmount; // {qBuyTok}
}
/**
* @title IBroker
* @notice The Broker deploys oneshot Trade contracts for Traders and monitors
* the continued proper functioning of trading platforms.
*/
interface IBroker is IComponent {
event GnosisSet(IGnosis oldVal, IGnosis newVal);
event BatchTradeImplementationSet(ITrade oldVal, ITrade newVal);
event DutchTradeImplementationSet(ITrade oldVal, ITrade newVal);
event BatchAuctionLengthSet(uint48 oldVal, uint48 newVal);
event DutchAuctionLengthSet(uint48 oldVal, uint48 newVal);
event BatchTradeDisabledSet(bool prevVal, bool newVal);
event DutchTradeDisabledSet(IERC20Metadata indexed erc20, bool prevVal, bool newVal);
// Initialization
function init(
IMain main_,
IGnosis gnosis_,
ITrade batchTradeImplemention_,
uint48 batchAuctionLength_,
ITrade dutchTradeImplemention_,
uint48 dutchAuctionLength_
) external;
/// Request a trade from the broker
/// @dev Requires setting an allowance in advance
/// @custom:interaction
function openTrade(
TradeKind kind,
TradeRequest memory req,
TradePrices memory prices
) external returns (ITrade);
/// Only callable by one of the trading contracts the broker deploys
function reportViolation() external;
function batchTradeDisabled() external view returns (bool);
function dutchTradeDisabled(IERC20Metadata erc20) external view returns (bool);
}
interface TestIBroker is IBroker {
function gnosis() external view returns (IGnosis);
function batchTradeImplementation() external view returns (ITrade);
function dutchTradeImplementation() external view returns (ITrade);
function batchAuctionLength() external view returns (uint48);
function dutchAuctionLength() external view returns (uint48);
function setGnosis(IGnosis newGnosis) external;
function setBatchTradeImplementation(ITrade newTradeImplementation) external;
function setBatchAuctionLength(uint48 newAuctionLength) external;
function setDutchTradeImplementation(ITrade newTradeImplementation) external;
function setDutchAuctionLength(uint48 newAuctionLength) external;
function enableBatchTrade() external;
function enableDutchTrade(IERC20Metadata erc20) external;
// only present on pre-3.0.0 Brokers; used by EasyAuction regression test
function disabled() external view returns (bool);
}
IGnosis.sol 49 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
struct GnosisAuctionData {
IERC20 auctioningToken;
IERC20 biddingToken;
uint256 orderCancellationEndDate;
uint256 auctionEndDate;
bytes32 initialAuctionOrder;
uint256 minimumBiddingAmountPerOrder;
uint256 interimSumBidAmount;
bytes32 interimOrder;
bytes32 clearingPriceOrder;
uint96 volumeClearingPriceOrder;
bool minFundingThresholdNotReached;
bool isAtomicClosureAllowed;
uint256 feeNumerator;
uint256 minFundingThreshold;
}
/// The relevant portion of the interface of the live Gnosis EasyAuction contract
/// https://github.com/gnosis/ido-contracts/blob/main/contracts/EasyAuction.sol
interface IGnosis {
function initiateAuction(
IERC20 auctioningToken,
IERC20 biddingToken,
uint256 orderCancellationEndDate,
uint256 auctionEndDate,
uint96 auctionedSellAmount,
uint96 minBuyAmount,
uint256 minimumBiddingAmountPerOrder,
uint256 minFundingThreshold,
bool isAtomicClosureAllowed,
address accessManagerContract,
bytes memory accessManagerContractData
) external returns (uint256 auctionId);
function auctionData(uint256 auctionId) external view returns (GnosisAuctionData memory);
/// @param auctionId The external auction id
/// @dev See here for decoding: https://git.io/JMang
/// @return encodedOrder The order, encoded in a bytes 32
function settleAuction(uint256 auctionId) external returns (bytes32 encodedOrder);
/// @return The numerator over a 1000-valued denominator
function feeNumerator() external returns (uint256);
}
IRToken.sol 158 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
// solhint-disable-next-line max-line-length
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol";
import "../libraries/Fixed.sol";
import "../libraries/Throttle.sol";
import "./IAsset.sol";
import "./IComponent.sol";
import "./IMain.sol";
import "./IRewardable.sol";
/**
* @title IRToken
* @notice An RToken is an ERC20 that is permissionlessly issuable/redeemable and tracks an
* exchange rate against a single unit: baskets, or {BU} in our type notation.
*/
interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradeable {
/// Emitted when an issuance of RToken occurs, whether it occurs via slow minting or not
/// @param issuer The address holding collateral tokens
/// @param recipient The address of the recipient of the RTokens
/// @param amount The quantity of RToken being issued
/// @param baskets The corresponding number of baskets
event Issuance(
address indexed issuer,
address indexed recipient,
uint256 amount,
uint192 baskets
);
/// Emitted when a redemption of RToken occurs
/// @param redeemer The address holding RToken
/// @param recipient The address of the account receiving the backing collateral tokens
/// @param amount The quantity of RToken being redeemed
/// @param baskets The corresponding number of baskets
/// @param amount {qRTok} The amount of RTokens canceled
event Redemption(
address indexed redeemer,
address indexed recipient,
uint256 amount,
uint192 baskets
);
/// Emitted when the number of baskets needed changes
/// @param oldBasketsNeeded Previous number of baskets units needed
/// @param newBasketsNeeded New number of basket units needed
event BasketsNeededChanged(uint192 oldBasketsNeeded, uint192 newBasketsNeeded);
/// Emitted when RToken is melted, i.e the RToken supply is decreased but basketsNeeded is not
/// @param amount {qRTok}
event Melted(uint256 amount);
/// Emitted when issuance SupplyThrottle params are set
event IssuanceThrottleSet(ThrottleLib.Params oldVal, ThrottleLib.Params newVal);
/// Emitted when redemption SupplyThrottle params are set
event RedemptionThrottleSet(ThrottleLib.Params oldVal, ThrottleLib.Params newVal);
// Initialization
function init(
IMain main_,
string memory name_,
string memory symbol_,
string memory mandate_,
ThrottleLib.Params calldata issuanceThrottleParams,
ThrottleLib.Params calldata redemptionThrottleParams
) external;
/// Issue an RToken with basket collateral
/// @param amount {qRTok} The quantity of RToken to issue
/// @custom:interaction
function issue(uint256 amount) external;
/// Issue an RToken with basket collateral, to a particular recipient
/// @param recipient The address to receive the issued RTokens
/// @param amount {qRTok} The quantity of RToken to issue
/// @custom:interaction
function issueTo(address recipient, uint256 amount) external;
/// Redeem RToken for basket collateral
/// @dev Use redeemCustom for non-current baskets
/// @param amount {qRTok} The quantity {qRToken} of RToken to redeem
/// @custom:interaction
function redeem(uint256 amount) external;
/// Redeem RToken for basket collateral to a particular recipient
/// @dev Use redeemCustom for non-current baskets
/// @param recipient The address to receive the backing collateral tokens
/// @param amount {qRTok} The quantity {qRToken} of RToken to redeem
/// @custom:interaction
function redeemTo(address recipient, uint256 amount) external;
/// Redeem RToken for a linear combination of historical baskets, to a particular recipient
/// @dev Allows partial redemptions up to the minAmounts
/// @param recipient The address to receive the backing collateral tokens
/// @param amount {qRTok} The quantity {qRToken} of RToken to redeem
/// @param basketNonces An array of basket nonces to do redemption from
/// @param portions {1} An array of Fix quantities that must add up to FIX_ONE
/// @param expectedERC20sOut An array of ERC20s expected out
/// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive
/// @custom:interaction
function redeemCustom(
address recipient,
uint256 amount,
uint48[] memory basketNonces,
uint192[] memory portions,
address[] memory expectedERC20sOut,
uint256[] memory minAmounts
) external;
/// Mint an amount of RToken equivalent to baskets BUs, scaling basketsNeeded up
/// Callable only by BackingManager
/// @param baskets {BU} The number of baskets to mint RToken for
/// @custom:protected
function mint(uint192 baskets) external;
/// Melt a quantity of RToken from the caller's account
/// @param amount {qRTok} The amount to be melted
/// @custom:protected
function melt(uint256 amount) external;
/// Burn an amount of RToken from caller's account and scale basketsNeeded down
/// Callable only by BackingManager
/// @custom:protected
function dissolve(uint256 amount) external;
/// Set the number of baskets needed directly, callable only by the BackingManager
/// @param basketsNeeded {BU} The number of baskets to target
/// needed range: pretty interesting
/// @custom:protected
function setBasketsNeeded(uint192 basketsNeeded) external;
/// @return {BU} How many baskets are being targeted
function basketsNeeded() external view returns (uint192);
/// @return {qRTok} The maximum issuance that can be performed in the current block
function issuanceAvailable() external view returns (uint256);
/// @return {qRTok} The maximum redemption that can be performed in the current block
function redemptionAvailable() external view returns (uint256);
}
interface TestIRToken is IRToken {
function setIssuanceThrottleParams(ThrottleLib.Params calldata) external;
function setRedemptionThrottleParams(ThrottleLib.Params calldata) external;
function issuanceThrottleParams() external view returns (ThrottleLib.Params memory);
function redemptionThrottleParams() external view returns (ThrottleLib.Params memory);
function increaseAllowance(address, uint256) external returns (bool);
function decreaseAllowance(address, uint256) external returns (bool);
function monetizeDonations(IERC20) external;
}
Throttle.sol 91 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "./Fixed.sol";
uint48 constant ONE_HOUR = 3600; // {seconds/hour}
/**
* @title ThrottleLib
* A library that implements a usage throttle that can be used to ensure net issuance
* or net redemption for an RToken never exceeds some bounds per unit time (hour).
*
* It is expected for the RToken to use this library with two instances, one for issuance
* and one for redemption. Issuance causes the available redemption amount to increase, and
* visa versa.
*/
library ThrottleLib {
using FixLib for uint192;
struct Params {
uint256 amtRate; // {qRTok/hour} a quantity of RToken hourly; cannot be 0
uint192 pctRate; // {1/hour} a fraction of RToken hourly; can be 0
}
struct Throttle {
// === Gov params ===
Params params;
// === Cache ===
uint48 lastTimestamp; // {seconds}
uint256 lastAvailable; // {qRTok}
}
/// Reverts if usage amount exceeds available amount
/// @param supply {qRTok} Total RToken supply beforehand
/// @param amount {qRTok} Amount of RToken to use. Should be negative for the issuance
/// throttle during redemption and for the redemption throttle during issuance.
function useAvailable(
Throttle storage throttle,
uint256 supply,
int256 amount
) internal {
// untestable: amtRate will always be greater > 0 due to previous validations
if (throttle.params.amtRate == 0 && throttle.params.pctRate == 0) return;
// Calculate hourly limit
uint256 limit = hourlyLimit(throttle, supply); // {qRTok}
// Calculate available amount before supply change
uint256 available = currentlyAvailable(throttle, limit);
// Update throttle.timestamp if available amount changed or at limit
if (available != throttle.lastAvailable || available == limit) {
throttle.lastTimestamp = uint48(block.timestamp);
}
// Update throttle.lastAvailable
if (amount > 0) {
require(uint256(amount) <= available, "supply change throttled");
available -= uint256(amount);
// untestable: the final else statement, amount will never be 0
} else if (amount < 0) {
available += uint256(-amount);
}
throttle.lastAvailable = available;
}
/// @param limit {qRTok/hour} The hourly limit
/// @return available {qRTok} Amount currently available for consumption
function currentlyAvailable(Throttle storage throttle, uint256 limit)
internal
view
returns (uint256 available)
{
uint48 delta = uint48(block.timestamp) - throttle.lastTimestamp; // {seconds}
available = throttle.lastAvailable + (limit * delta) / ONE_HOUR;
if (available > limit) available = limit;
}
/// @return limit {qRTok} The hourly limit
function hourlyLimit(Throttle storage throttle, uint256 supply)
internal
view
returns (uint256 limit)
{
Params storage params = throttle.params;
// Calculate hourly limit as: max(params.amtRate, supply.mul(params.pctRate))
limit = (supply * params.pctRate) / FIX_ONE_256; // {qRTok}
if (params.amtRate > limit) limit = params.amtRate;
}
}
IFurnace.sol 35 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "../libraries/Fixed.sol";
import "./IComponent.sol";
/**
* @title IFurnace
* @notice A helper contract to burn RTokens slowly and permisionlessly.
*/
interface IFurnace is IComponent {
// Initialization
function init(IMain main_, uint192 ratio_) external;
/// Emitted when the melting ratio is changed
/// @param oldRatio The old ratio
/// @param newRatio The new ratio
event RatioSet(uint192 oldRatio, uint192 newRatio);
function ratio() external view returns (uint192);
/// Needed value range: [0, 1], granularity 1e-9
/// @custom:governance
function setRatio(uint192) external;
/// Performs any RToken melting that has vested since the last payout.
/// @custom:refresher
function melt() external;
}
interface TestIFurnace is IFurnace {
function lastPayout() external view returns (uint256);
function lastPayoutBal() external view returns (uint256);
}
ITrading.sol 75 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../libraries/Fixed.sol";
import "./IAsset.sol";
import "./IComponent.sol";
import "./ITrade.sol";
import "./IRewardable.sol";
/**
* @title ITrading
* @notice Common events and refresher function for all Trading contracts
*/
interface ITrading is IComponent, IRewardableComponent {
event MaxTradeSlippageSet(uint192 oldVal, uint192 newVal);
event MinTradeVolumeSet(uint192 oldVal, uint192 newVal);
/// Emitted when a trade is started
/// @param trade The one-time-use trade contract that was just deployed
/// @param sell The token to sell
/// @param buy The token to buy
/// @param sellAmount {qSellTok} The quantity of the selling token
/// @param minBuyAmount {qBuyTok} The minimum quantity of the buying token to accept
event TradeStarted(
ITrade indexed trade,
IERC20 indexed sell,
IERC20 indexed buy,
uint256 sellAmount,
uint256 minBuyAmount
);
/// Emitted after a trade ends
/// @param trade The one-time-use trade contract
/// @param sell The token to sell
/// @param buy The token to buy
/// @param sellAmount {qSellTok} The quantity of the token sold
/// @param buyAmount {qBuyTok} The quantity of the token bought
event TradeSettled(
ITrade indexed trade,
IERC20 indexed sell,
IERC20 indexed buy,
uint256 sellAmount,
uint256 buyAmount
);
/// Settle a single trade, expected to be used with multicall for efficient mass settlement
/// @param sell The sell token in the trade
/// @return The trade settled
/// @custom:refresher
function settleTrade(IERC20 sell) external returns (ITrade);
/// @return {%} The maximum trade slippage acceptable
function maxTradeSlippage() external view returns (uint192);
/// @return {UoA} The minimum trade volume in UoA, applies to all assets
function minTradeVolume() external view returns (uint192);
/// @return The ongoing trade for a sell token, or the zero address
function trades(IERC20 sell) external view returns (ITrade);
/// @return The number of ongoing trades open
function tradesOpen() external view returns (uint48);
/// @return The number of total trades ever opened
function tradesNonce() external view returns (uint256);
}
interface TestITrading is ITrading {
/// @custom:governance
function setMaxTradeSlippage(uint192 val) external;
/// @custom:governance
function setMinTradeVolume(uint192 val) external;
}
Allowance.sol 43 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
interface IERC20ApproveOnly {
function approve(address spender, uint256 value) external;
function allowance(address owner, address spender) external view returns (uint256);
}
library AllowanceLib {
/// An approve helper that:
/// 1. Sets initial allowance to 0
/// 2. Tries to set the provided allowance
/// 3. Falls back to setting a maximum allowance, if (2) fails
/// Context: Some new-age ERC20s think it's a good idea to revert for allowances
/// that are > 0 but < type(uint256).max.
function safeApproveFallbackToMax(
address tokenAddress,
address spender,
uint256 value
) internal {
IERC20ApproveOnly token = IERC20ApproveOnly(tokenAddress);
// 1. Set initial allowance to 0
token.approve(spender, 0);
require(token.allowance(address(this), spender) == 0, "allowance not 0");
if (value == 0) return;
// 2. Try to set the provided allowance
bool success; // bool success = false;
try token.approve(spender, value) {
success = token.allowance(address(this), spender) == value;
// solhint-disable-next-line no-empty-blocks
} catch {}
// 3. Fall-back to setting a maximum allowance
if (!success) {
token.approve(spender, type(uint256).max);
require(token.allowance(address(this), spender) >= value, "allowance missing");
}
}
}
IActFacet.sol 73 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "../interfaces/IBackingManager.sol";
import "../interfaces/IStRSRVotes.sol";
import "../interfaces/IRevenueTrader.sol";
import "../interfaces/IRToken.sol";
/**
* @title IActFacet
* @notice A Facade to help batch compound actions that cannot be done from an EOA, solely.
v */
interface IActFacet {
/// Claims rewards from all places they can accrue.
function claimRewards(IRToken rToken) external;
/// To use this, first call:
/// - IReadFacet.auctionsSettleable(revenueTrader)
/// - IReadFacet.revenueOverview(revenueTrader)
/// If either arrays returned are non-empty, then can execute this function productively.
/// Logic:
/// For each ERC20 in `toSettle`:
/// - Settle any open ERC20 trades
/// Then:
/// - Transfer any revenue for that ERC20 from the backingManager to revenueTrader
/// - Call `revenueTrader.manageTokens(ERC20)` to start an auction
function runRevenueAuctions(
IRevenueTrader revenueTrader,
IERC20[] memory toSettle,
IERC20[] memory toStart,
TradeKind[] memory kinds
) external;
// === Static Calls ===
/// To use this, call via callStatic.
/// Includes consideration of when to distribute the RevenueTrader tokenToBuy
/// @return erc20s The ERC20s that have auctions that can be started
/// @return canStart If the ERC20 auction can be started
/// @return surpluses {qTok} The surplus amounts currently held, ignoring reward balances
/// @return minTradeAmounts {qTok} The minimum amount worth trading
/// @return bmRewards {qTok} The amounts would be claimed by backingManager.claimRewards()
/// @return revTraderRewards {qTok} The amounts that would be claimed by trader.claimRewards()
/// @dev Note that `surpluses` + `bmRewards` + `revTraderRewards`
/// @custom:static-call
function revenueOverview(IRevenueTrader revenueTrader)
external
returns (
IERC20[] memory erc20s,
bool[] memory canStart,
uint256[] memory surpluses,
uint256[] memory minTradeAmounts,
uint256[] memory bmRewards,
uint256[] memory revTraderRewards
);
/// To use this, call via callStatic.
/// If canStart is true, call backingManager.rebalance(). May require settling a
/// trade first; see auctionsSettleable.
/// @return canStart true iff a recollateralization auction can be started
/// @return sell The sell token in the auction
/// @return buy The buy token in the auction
/// @return sellAmount {qSellTok} How much would be sold
/// @custom:static-call
function nextRecollateralizationAuction(IBackingManager bm, TradeKind kind)
external
returns (
bool canStart,
IERC20 sell,
IERC20 buy,
uint256 sellAmount
);
}
IComponent.sol 15 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "./IMain.sol";
import "./IVersioned.sol";
/**
* @title IComponent
* @notice A Component is the central building block of all our system contracts. Components
* contain important state that must be migrated during upgrades, and they delegate
* their ownership to Main's owner.
*/
interface IComponent is IVersioned {
function main() external view returns (IMain);
}
IVersioned.sol 6 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
interface IVersioned {
function version() external view returns (string memory);
}
ActFacet.sol 287 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Multicall.sol";
import "../../plugins/trading/DutchTrade.sol";
import "../../plugins/trading/GnosisTrade.sol";
import "../../interfaces/IActFacet.sol";
import "../../interfaces/IBackingManager.sol";
/**
* @title ActFacet
* @notice
* Facet to help batch compound actions that cannot be done from an EOA, solely.
* Compatible with both 2.1.0 and ^3.0.0 RTokens.
* @custom:static-call - Use ethers callStatic() to get result after update; do not execute
*/
// slither-disable-start
contract ActFacet is IActFacet, Multicall {
using Address for address;
using SafeERC20 for IERC20;
using FixLib for uint192;
function claimRewards(IRToken rToken) external {
IMain main = rToken.main();
main.backingManager().claimRewards();
main.rTokenTrader().claimRewards();
main.rsrTrader().claimRewards();
}
/// To use this, first call:
/// - auctionsSettleable(revenueTrader)
/// - revenueOverview(revenueTrader)
/// If either arrays returned are non-empty, then can execute this function productively.
/// Logic:
/// For each ERC20 in `toSettle`:
/// - Settle any open ERC20 trades
/// Then:
/// - Call `revenueTrader.manageTokens(ERC20)` to start an auction
function runRevenueAuctions(
IRevenueTrader revenueTrader,
IERC20[] calldata toSettle,
IERC20[] calldata toStart,
TradeKind[] calldata kinds
) external {
// Settle auctions
for (uint256 i = 0; i < toSettle.length; ++i) {
_settleTrade(revenueTrader, toSettle[i]);
}
// if 2.1.0, distribute tokenToBuy
bytes1 majorVersion = bytes(revenueTrader.version())[0];
if (toSettle.length > 0 && (majorVersion == bytes1("2") || majorVersion == bytes1("1"))) {
address(revenueTrader).functionCall(
abi.encodeWithSignature("manageToken(address)", revenueTrader.tokenToBuy())
);
}
if (toStart.length == 0) return;
// Transfer revenue backingManager -> revenueTrader
_forwardRevenue(revenueTrader.main().backingManager(), toStart);
// Start RevenueTrader auctions
_runRevenueAuctions(revenueTrader, toStart, kinds);
}
// === Static Calls ===
/// To use this, call via callStatic.
/// Includes consideration of when to distribute the RevenueTrader tokenToBuy
/// @return erc20s The ERC20s that have auctions that can be started
/// @return canStart If the ERC20 auction can be started
/// @return surpluses {qTok} The surplus amounts currently held, ignoring reward balances
/// @return minTradeAmounts {qTok} The minimum amount worth trading
/// @return bmRewards {qTok} The amounts would be claimed by backingManager.claimRewards()
/// @return revTraderRewards {qTok} The amounts that would be claimed by trader.claimRewards()
/// @dev Note that `surpluses` + `bmRewards` + `revTraderRewards`
/// @custom:static-call
function revenueOverview(IRevenueTrader revenueTrader)
external
returns (
IERC20[] memory erc20s,
bool[] memory canStart,
uint256[] memory surpluses,
uint256[] memory minTradeAmounts,
uint256[] memory bmRewards,
uint256[] memory revTraderRewards
)
{
IBackingManager bm = revenueTrader.main().backingManager();
uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA}
Registry memory reg = revenueTrader.main().assetRegistry().getRegistry();
// Forward ALL revenue
_forwardRevenue(bm, reg.erc20s);
erc20s = new IERC20[](reg.erc20s.length);
canStart = new bool[](reg.erc20s.length);
surpluses = new uint256[](reg.erc20s.length);
minTradeAmounts = new uint256[](reg.erc20s.length);
bmRewards = new uint256[](reg.erc20s.length);
revTraderRewards = new uint256[](reg.erc20s.length);
// Calculate which erc20s should have auctions started
for (uint256 i = 0; i < reg.erc20s.length; ++i) {
erc20s[i] = reg.erc20s[i];
// Settle first if possible. Required so we can assess full available balance
ITrade trade = revenueTrader.trades(erc20s[i]);
if (address(trade) != address(0) && trade.canSettle()) {
_settleTrade(revenueTrader, erc20s[i]);
}
surpluses[i] = erc20s[i].balanceOf(address(revenueTrader));
(uint192 low, ) = reg.assets[i].price(); // {UoA/tok}
if (low == 0) continue;
// {qTok} = {UoA} / {UoA/tok}
minTradeAmounts[i] = minTradeVolume.safeDiv(low, FLOOR).shiftl_toUint(
int8(reg.assets[i].erc20Decimals())
);
if (
surpluses[i] > minTradeAmounts[i] &&
revenueTrader.trades(erc20s[i]) == ITrade(address(0))
) {
canStart[i] = true;
}
}
// Calculate rewards
// Reward counts are disjoint with `surpluses` and `canStart`
for (uint256 i = 0; i < reg.erc20s.length; ++i) {
bmRewards[i] = reg.erc20s[i].balanceOf(address(bm));
}
// solhint-disable-next-line no-empty-blocks
try bm.claimRewards() {} catch {} // same between 2.1.0 and 3.0.0
for (uint256 i = 0; i < reg.erc20s.length; ++i) {
bmRewards[i] = reg.erc20s[i].balanceOf(address(bm)) - bmRewards[i];
}
for (uint256 i = 0; i < reg.erc20s.length; ++i) {
revTraderRewards[i] = reg.erc20s[i].balanceOf(address(revenueTrader));
}
// solhint-disable-next-line no-empty-blocks
try revenueTrader.claimRewards() {} catch {} // same between 2.1.0 and 3.0.0
for (uint256 i = 0; i < reg.erc20s.length; ++i) {
revTraderRewards[i] =
reg.erc20s[i].balanceOf(address(revenueTrader)) -
revTraderRewards[i];
}
}
/// To use this, call via callStatic.
/// If canStart is true, call backingManager.rebalance(). May require settling a
/// trade first; see auctionsSettleable.
/// @return canStart true iff a recollateralization auction can be started
/// @return sell The sell token in the auction
/// @return buy The buy token in the auction
/// @return sellAmount {qSellTok} How much would be sold
/// @custom:static-call
function nextRecollateralizationAuction(IBackingManager bm, TradeKind kind)
external
returns (
bool canStart,
IERC20 sell,
IERC20 buy,
uint256 sellAmount
)
{
IERC20[] memory erc20s = bm.main().assetRegistry().erc20s();
// Settle any settle-able open trades
if (bm.tradesOpen() > 0) {
for (uint256 i = 0; i < erc20s.length; ++i) {
ITrade trade = bm.trades(erc20s[i]);
if (address(trade) != address(0) && trade.canSettle()) {
_settleTrade(bm, erc20s[i]);
break; // backingManager can only have 1 trade open at a time
}
}
}
// If no auctions ongoing, to find a new auction to start
if (bm.tradesOpen() == 0) {
_rebalance(bm, kind);
// Find the started auction
for (uint256 i = 0; i < erc20s.length; ++i) {
ITrade trade = ITrade(address(bm.trades(erc20s[i])));
if (address(trade) != address(0)) {
canStart = true;
sell = trade.sell();
buy = trade.buy();
sellAmount = _getSellAmount(trade);
}
}
}
}
// === Private ===
function _getSellAmount(ITrade trade) private view returns (uint256) {
if (trade.KIND() == TradeKind.DUTCH_AUCTION) {
return
DutchTrade(address(trade)).sellAmount().shiftl_toUint(
int8(trade.sell().decimals())
);
} else if (trade.KIND() == TradeKind.BATCH_AUCTION) {
return GnosisTrade(address(trade)).initBal();
} else {
revert("invalid trade type");
}
}
function _settleTrade(ITrading trader, IERC20 toSettle) private {
bytes1 majorVersion = bytes(trader.version())[0];
if (majorVersion == bytes1("3")) {
// Settle auctions
trader.settleTrade(toSettle);
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
address(trader).functionCall(abi.encodeWithSignature("settleTrade(address)", toSettle));
} else {
_revertUnrecognizedVersion();
}
}
function _forwardRevenue(IBackingManager bm, IERC20[] memory toStart) private {
bytes1 majorVersion = bytes(bm.version())[0];
// Need to use try-catch here in order to still show revenueOverview when basket not ready
if (majorVersion == bytes1("3")) {
// solhint-disable-next-line no-empty-blocks
try bm.forwardRevenue(toStart) {} catch {}
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(bm).call{ value: 0 }(
abi.encodeWithSignature("manageTokens(address[])", toStart)
);
success = success; // hush warning
} else {
_revertUnrecognizedVersion();
}
}
function _runRevenueAuctions(
IRevenueTrader revenueTrader,
IERC20[] memory toStart,
TradeKind[] memory kinds
) private {
bytes1 majorVersion = bytes(revenueTrader.version())[0];
if (majorVersion == bytes1("3")) {
revenueTrader.manageTokens(toStart, kinds);
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
for (uint256 i = 0; i < toStart.length; ++i) {
address(revenueTrader).functionCall(
abi.encodeWithSignature("manageToken(address)", toStart[i])
);
}
} else {
_revertUnrecognizedVersion();
}
}
function _rebalance(IBackingManager bm, TradeKind kind) private {
bytes1 majorVersion = bytes(bm.version())[0];
if (majorVersion == bytes1("3")) {
// solhint-disable-next-line no-empty-blocks
try bm.rebalance(kind) {} catch {}
} else if (majorVersion == bytes1("2") || majorVersion == bytes1("1")) {
IERC20[] memory emptyERC20s = new IERC20[](0);
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(bm).call{ value: 0 }(
abi.encodeWithSignature("manageTokens(address[])", emptyERC20s)
);
success = success; // hush warning
} else {
_revertUnrecognizedVersion();
}
}
function _revertUnrecognizedVersion() private pure {
revert("unrecognized version");
}
}
// slither-disable-end
IRewardable.sol 33 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IComponent.sol";
import "./IMain.sol";
/**
* @title IRewardable
* @notice A simple interface mixin to support claiming of rewards.
*/
interface IRewardable {
/// Emitted whenever a reward token balance is claimed
/// @param erc20 The ERC20 of the reward token
/// @param amount {qTok}
event RewardsClaimed(IERC20 indexed erc20, uint256 amount);
/// Claim rewards earned by holding a balance of the ERC20 token
/// Must emit `RewardsClaimed` for each token rewards are claimed for
/// @custom:interaction
function claimRewards() external;
}
/**
* @title IRewardableComponent
* @notice A simple interface mixin to support claiming of rewards.
*/
interface IRewardableComponent is IRewardable {
/// Claim rewards for a single ERC20
/// Must emit `RewardsClaimed` for each token rewards are claimed for
/// @custom:interaction
function claimRewardsSingle(IERC20 erc20) external;
}
IStRSRVotes.sol 16 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol";
interface IStRSRVotes is IVotesUpgradeable {
/// @return The current era
function currentEra() external view returns (uint256);
/// @return The era at a past block number
function getPastEra(uint256 blockNumber) external view returns (uint256);
/// Stakes an RSR `amount` on the corresponding RToken and allows to delegate
/// votes from the sender to `delegatee` or self
function stakeAndDelegate(uint256 amount, address delegatee) external;
}
IDistributor.sol 64 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IComponent.sol";
uint256 constant MAX_DISTRIBUTION = 1e4; // 10,000
uint8 constant MAX_DESTINATIONS = 100; // maximum number of RevenueShare destinations
struct RevenueShare {
uint16 rTokenDist; // {revShare} A value between [0, 10,000]
uint16 rsrDist; // {revShare} A value between [0, 10,000]
}
/// Assumes no more than 100 independent distributions.
struct RevenueTotals {
uint24 rTokenTotal; // {revShare}
uint24 rsrTotal; // {revShare}
}
/**
* @title IDistributor
* @notice The Distributor Component maintains a revenue distribution table that dictates
* how to divide revenue across the Furnace, StRSR, and any other destinations.
*/
interface IDistributor is IComponent {
/// Emitted when a distribution is set
/// @param dest The address set to receive the distribution
/// @param rTokenDist The distribution of RToken that should go to `dest`
/// @param rsrDist The distribution of RSR that should go to `dest`
event DistributionSet(address indexed dest, uint16 rTokenDist, uint16 rsrDist);
/// Emitted when revenue is distributed
/// @param erc20 The token being distributed, either RSR or the RToken itself
/// @param source The address providing the revenue
/// @param amount The amount of the revenue
event RevenueDistributed(IERC20 indexed erc20, address indexed source, uint256 amount);
// Initialization
function init(IMain main_, RevenueShare memory dist) external;
/// @custom:governance
function setDistribution(address dest, RevenueShare memory share) external;
/// Distribute the `erc20` token across all revenue destinations
/// Only callable by RevenueTraders
/// @custom:protected
function distribute(IERC20 erc20, uint256 amount) external;
/// @return revTotals The total of all destinations
function totals() external view returns (RevenueTotals memory revTotals);
}
interface TestIDistributor is IDistributor {
// solhint-disable-next-line func-name-mixedcase
function FURNACE() external view returns (address);
// solhint-disable-next-line func-name-mixedcase
function ST_RSR() external view returns (address);
/// @return rTokenDist The RToken distribution for the address
/// @return rsrDist The RSR distribution for the address
function distribution(address) external view returns (uint16 rTokenDist, uint16 rsrDist);
}
IAssetRegistry.sol 76 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IAsset.sol";
import "./IComponent.sol";
/// A serialization of the AssetRegistry to be passed around in the P1 impl for gas optimization
struct Registry {
IERC20[] erc20s;
IAsset[] assets;
}
/**
* @title IAssetRegistry
* @notice The AssetRegistry is in charge of maintaining the ERC20 tokens eligible
* to be handled by the rest of the system. If an asset is in the registry, this means:
* 1. Its ERC20 contract has been vetted
* 2. The asset is the only asset for that ERC20
* 3. The asset can be priced in the UoA, usually via an oracle
*/
interface IAssetRegistry is IComponent {
/// Emitted when an asset is added to the registry
/// @param erc20 The ERC20 contract for the asset
/// @param asset The asset contract added to the registry
event AssetRegistered(IERC20 indexed erc20, IAsset indexed asset);
/// Emitted when an asset is removed from the registry
/// @param erc20 The ERC20 contract for the asset
/// @param asset The asset contract removed from the registry
event AssetUnregistered(IERC20 indexed erc20, IAsset indexed asset);
// Initialization
function init(IMain main_, IAsset[] memory assets_) external;
/// Fully refresh all asset state
/// @custom:refresher
function refresh() external;
/// Register `asset`
/// If either the erc20 address or the asset was already registered, fail
/// @return true if the erc20 address was not already registered.
/// @custom:governance
function register(IAsset asset) external returns (bool);
/// Register `asset` if and only if its erc20 address is already registered.
/// If the erc20 address was not registered, revert.
/// @return swapped If the asset was swapped for a previously-registered asset
/// @custom:governance
function swapRegistered(IAsset asset) external returns (bool swapped);
/// Unregister an asset, requiring that it is already registered
/// @custom:governance
function unregister(IAsset asset) external;
/// @return {s} The timestamp of the last refresh
function lastRefresh() external view returns (uint48);
/// @return The corresponding asset for ERC20, or reverts if not registered
function toAsset(IERC20 erc20) external view returns (IAsset);
/// @return The corresponding collateral, or reverts if unregistered or not collateral
function toColl(IERC20 erc20) external view returns (ICollateral);
/// @return If the ERC20 is registered
function isRegistered(IERC20 erc20) external view returns (bool);
/// @return A list of all registered ERC20s
function erc20s() external view returns (IERC20[] memory);
/// @return reg The list of registered ERC20s and Assets, in the same order
function getRegistry() external view returns (Registry memory reg);
/// @return The number of registered ERC20s
function size() external view returns (uint256);
}
IBasketHandler.sol 181 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../libraries/Fixed.sol";
import "./IAsset.sol";
import "./IComponent.sol";
struct BasketRange {
uint192 bottom; // {BU}
uint192 top; // {BU}
}
/**
* @title IBasketHandler
* @notice The BasketHandler aims to maintain a reference basket of constant target unit amounts.
* When a collateral token defaults, a new reference basket of equal target units is set.
* When _all_ collateral tokens default for a target unit, only then is the basket allowed to fall
* in terms of target unit amounts. The basket is considered defaulted in this case.
*/
interface IBasketHandler is IComponent {
/// Emitted when the prime basket is set
/// @param erc20s The collateral tokens for the prime basket
/// @param targetAmts {target/BU} A list of quantities of target unit per basket unit
/// @param targetNames Each collateral token's targetName
event PrimeBasketSet(IERC20[] erc20s, uint192[] targetAmts, bytes32[] targetNames);
/// Emitted when the reference basket is set
/// @param nonce {basketNonce} The basket nonce
/// @param erc20s The list of collateral tokens in the reference basket
/// @param refAmts {ref/BU} The reference amounts of the basket collateral tokens
/// @param disabled True when the list of erc20s + refAmts may not be correct
event BasketSet(uint256 indexed nonce, IERC20[] erc20s, uint192[] refAmts, bool disabled);
/// Emitted when a backup config is set for a target unit
/// @param targetName The name of the target unit as a bytes32
/// @param max The max number to use from `erc20s`
/// @param erc20s The set of backup collateral tokens
event BackupConfigSet(bytes32 indexed targetName, uint256 max, IERC20[] erc20s);
/// Emitted when the warmup period is changed
/// @param oldVal The old warmup period
/// @param newVal The new warmup period
event WarmupPeriodSet(uint48 oldVal, uint48 newVal);
/// Emitted when the status of a basket has changed
/// @param oldStatus The previous basket status
/// @param newStatus The new basket status
event BasketStatusChanged(CollateralStatus oldStatus, CollateralStatus newStatus);
/// Emitted when the last basket nonce available for redemption is changed
/// @param oldVal The old value of lastCollateralized
/// @param newVal The new value of lastCollateralized
event LastCollateralizedChanged(uint48 oldVal, uint48 newVal);
// Initialization
function init(
IMain main_,
uint48 warmupPeriod_,
bool reweightable_
) external;
/// Set the prime basket
/// For an index RToken (reweightable = true), use forceSetPrimeBasket to skip normalization
/// @param erc20s The collateral tokens for the new prime basket
/// @param targetAmts The target amounts (in) {target/BU} for the new prime basket
/// required range: 1e9 values; absolute range irrelevant.
/// @custom:governance
function setPrimeBasket(IERC20[] calldata erc20s, uint192[] calldata targetAmts) external;
/// Set the prime basket without normalizing targetAmts by the UoA of the current basket
/// Works the same as setPrimeBasket for non-index RTokens (reweightable = false)
/// @param erc20s The collateral tokens for the new prime basket
/// @param targetAmts The target amounts (in) {target/BU} for the new prime basket
/// required range: 1e9 values; absolute range irrelevant.
/// @custom:governance
function forceSetPrimeBasket(IERC20[] calldata erc20s, uint192[] calldata targetAmts) external;
/// Set the backup configuration for a given target
/// @param targetName The name of the target as a bytes32
/// @param max The maximum number of collateral tokens to use from this target
/// Required range: 1-255
/// @param erc20s A list of ordered backup collateral tokens
/// @custom:governance
function setBackupConfig(
bytes32 targetName,
uint256 max,
IERC20[] calldata erc20s
) external;
/// Default the basket in order to schedule a basket refresh
/// @custom:protected
function disableBasket() external;
/// Governance-controlled setter to cause a basket switch explicitly
/// @custom:governance
/// @custom:interaction
function refreshBasket() external;
/// Track the basket status changes
/// @custom:refresher
function trackStatus() external;
/// Track when last collateralized
/// @custom:refresher
function trackCollateralization() external;
/// @return If the BackingManager has sufficient collateral to redeem the entire RToken supply
function fullyCollateralized() external view returns (bool);
/// @return status The worst CollateralStatus of all collateral in the basket
function status() external view returns (CollateralStatus status);
/// @return If the basket is ready to issue and trade
function isReady() external view returns (bool);
/// @param erc20 The ERC20 token contract for the asset
/// @return {tok/BU} The whole token quantity of token in the reference basket
/// Returns 0 if erc20 is not registered or not in the basket
/// Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0.
/// Otherwise, returns (token's basket.refAmts / token's Collateral.refPerTok())
function quantity(IERC20 erc20) external view returns (uint192);
/// Like quantity(), but unsafe because it DOES NOT CONFIRM THAT THE ASSET IS CORRECT
/// @param erc20 The ERC20 token contract for the asset
/// @param asset The registered asset plugin contract for the erc20
/// @return {tok/BU} The whole token quantity of token in the reference basket
/// Returns 0 if erc20 is not registered or not in the basket
/// Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0.
/// Otherwise, returns (token's basket.refAmts / token's Collateral.refPerTok())
function quantityUnsafe(IERC20 erc20, IAsset asset) external view returns (uint192);
/// @param amount {BU}
/// @return erc20s The addresses of the ERC20 tokens in the reference basket
/// @return quantities {qTok} The quantity of each ERC20 token to issue `amount` baskets
function quote(uint192 amount, RoundingMode rounding)
external
view
returns (address[] memory erc20s, uint256[] memory quantities);
/// Return the redemption value of `amount` BUs for a linear combination of historical baskets
/// @param basketNonces An array of basket nonces to do redemption from
/// @param portions {1} An array of Fix quantities that must add up to FIX_ONE
/// @param amount {BU}
/// @return erc20s The backing collateral erc20s
/// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs
function quoteCustomRedemption(
uint48[] memory basketNonces,
uint192[] memory portions,
uint192 amount
) external view returns (address[] memory erc20s, uint256[] memory quantities);
/// @return top {BU} The number of partial basket units: e.g max(coll.map((c) => c.balAsBUs())
/// bottom {BU} The number of whole basket units held by the account
function basketsHeldBy(address account) external view returns (BasketRange memory);
/// Should not revert
/// low should be nonzero when BUs are worth selling
/// @return low {UoA/BU} The lower end of the price estimate
/// @return high {UoA/BU} The upper end of the price estimate
function price() external view returns (uint192 low, uint192 high);
/// Should not revert
/// lotLow should be nonzero if a BU could be worth selling
/// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility
/// @return lotLow {UoA/tok} The lower end of the lot price estimate
/// @return lotHigh {UoA/tok} The upper end of the lot price estimate
function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh);
/// @return timestamp The timestamp at which the basket was last set
function timestamp() external view returns (uint48);
/// @return The current basket nonce, regardless of status
function nonce() external view returns (uint48);
}
interface TestIBasketHandler is IBasketHandler {
function warmupPeriod() external view returns (uint48);
function setWarmupPeriod(uint48 val) external;
}
IRevenueTrader.sol 45 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "./IBroker.sol";
import "./IComponent.sol";
import "./ITrading.sol";
/**
* @title IRevenueTrader
* @notice The RevenueTrader is an extension of the trading mixin that trades all
* assets at its address for a single target asset. There are two runtime instances
* of the RevenueTrader, 1 for RToken and 1 for RSR.
*/
interface IRevenueTrader is IComponent, ITrading {
// Initialization
function init(
IMain main_,
IERC20 tokenToBuy_,
uint192 maxTradeSlippage_,
uint192 minTradeVolume_
) external;
/// Distribute tokenToBuy to its destinations
/// @dev Special-case of manageTokens()
/// @custom:interaction
function distributeTokenToBuy() external;
/// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0
/// @custom:interaction
function returnTokens(IERC20[] memory erc20s) external;
/// Process some number of tokens
/// If the tokenToBuy is included in erc20s, RevenueTrader will distribute it at end of the tx
/// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered
/// @param kinds The kinds of auctions to launch: DUTCH_AUCTION | BATCH_AUCTION
/// @custom:interaction
function manageTokens(IERC20[] memory erc20s, TradeKind[] memory kinds) external;
function tokenToBuy() external view returns (IERC20);
}
// solhint-disable-next-line no-empty-blocks
interface TestIRevenueTrader is IRevenueTrader, TestITrading {
}
IBackingManager.sol 98 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IAssetRegistry.sol";
import "./IBasketHandler.sol";
import "./IBroker.sol";
import "./IComponent.sol";
import "./IRToken.sol";
import "./IStRSR.sol";
import "./ITrading.sol";
/// Memory struct for RecollateralizationLibP1 + RTokenAsset
/// Struct purposes:
/// 1. Configure trading
/// 2. Stay under stack limit with fewer vars
/// 3. Cache information such as component addresses and basket quantities, to save on gas
struct TradingContext {
BasketRange basketsHeld; // {BU}
// basketsHeld.top is the number of partial baskets units held
// basketsHeld.bottom is the number of full basket units held
// Components
IBasketHandler bh;
IAssetRegistry ar;
IStRSR stRSR;
IERC20 rsr;
IRToken rToken;
// Gov Vars
uint192 minTradeVolume; // {UoA}
uint192 maxTradeSlippage; // {1}
// Cached values
uint192[] quantities; // {tok/BU} basket quantities
uint192[] bals; // {tok} balances in BackingManager + out on trades
}
/**
* @title IBackingManager
* @notice The BackingManager handles changes in the ERC20 balances that back an RToken.
* - It computes which trades to perform, if any, and initiates these trades with the Broker.
* - rebalance()
* - If already collateralized, excess assets are transferred to RevenueTraders.
* - forwardRevenue(IERC20[] calldata erc20s)
*/
interface IBackingManager is IComponent, ITrading {
/// Emitted when the trading delay is changed
/// @param oldVal The old trading delay
/// @param newVal The new trading delay
event TradingDelaySet(uint48 oldVal, uint48 newVal);
/// Emitted when the backing buffer is changed
/// @param oldVal The old backing buffer
/// @param newVal The new backing buffer
event BackingBufferSet(uint192 oldVal, uint192 newVal);
// Initialization
function init(
IMain main_,
uint48 tradingDelay_,
uint192 backingBuffer_,
uint192 maxTradeSlippage_,
uint192 minTradeVolume_
) external;
// Give RToken max allowance over a registered token
/// @custom:refresher
/// @custom:interaction
function grantRTokenAllowance(IERC20) external;
/// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable
/// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION
/// @custom:interaction RCEI
function rebalance(TradeKind kind) external;
/// Forward revenue to RevenueTraders; reverts if not fully collateralized
/// @param erc20s The tokens to forward
/// @custom:interaction RCEI
function forwardRevenue(IERC20[] calldata erc20s) external;
/// Structs for trading
/// @param basketsHeld The number of baskets held by the BackingManager
/// @return ctx The TradingContext
/// @return reg Contents of AssetRegistry.getRegistry()
function tradingContext(BasketRange memory basketsHeld)
external
view
returns (TradingContext memory ctx, Registry memory reg);
}
interface TestIBackingManager is IBackingManager, TestITrading {
function tradingDelay() external view returns (uint48);
function backingBuffer() external view returns (uint192);
function setTradingDelay(uint48 val) external;
function setBackingBuffer(uint192 val) external;
}
NetworkConfigLib.sol 26 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
/**
* @title NetworkConfigLib
* @notice Provides network-specific configuration parameters
*/
library NetworkConfigLib {
error InvalidNetwork();
// Returns the blocktime based on the current network (e.g. 12s for Ethereum PoS)
// See docs/system-design.md for discussion of handling longer or shorter times
function blocktime() internal view returns (uint48) {
uint256 chainId = block.chainid;
// untestable:
// most of the branches will be shown as uncovered, because we only run coverage
// on local Ethereum PoS network (31337). Manual testing was performed.
if (chainId == 1 || chainId == 3 || chainId == 5 || chainId == 31337) {
return 12; // Ethereum PoS, Goerli, HH (tests)
} else if (chainId == 8453 || chainId == 84531) {
return 2; // Base, Base Goerli
} else {
revert InvalidNetwork();
}
}
}
DutchTrade.sol 380 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../../libraries/Fixed.sol";
import "../../libraries/NetworkConfigLib.sol";
import "../../interfaces/IAsset.sol";
import "../../interfaces/IBroker.sol";
import "../../interfaces/ITrade.sol";
interface IDutchTradeCallee {
function dutchTradeCallback(
address buyToken,
// {qBuyTok}
uint256 buyAmount,
bytes calldata data
) external;
}
enum BidType {
NONE,
CALLBACK,
TRANSFER
}
// A dutch auction in 4 parts:
// 1. 0% - 20%: Geometric decay from 1000x the bestPrice to ~1.5x the bestPrice
// 2. 20% - 45%: Linear decay from ~1.5x the bestPrice to the bestPrice
// 3. 45% - 95%: Linear decay from the bestPrice to the worstPrice
// 4. 95% - 100%: Constant at the worstPrice
//
// For a trade between 2 assets with 1% oracleError:
// A 30-minute auction on a chain with a 12-second blocktime has a ~20% price drop per block
// during the 1st period, ~0.8% during the 2nd period, and ~0.065% during the 3rd period.
//
// 30-minutes is the recommended length of auction for a chain with 12-second blocktimes.
// 6 minutes, 7.5 minutes, 15 minutes, 1.5 minutes for each pariod respectively.
//
// Longer and shorter times can be used as well. The pricing method does not degrade
// beyond the degree to which less overall blocktime means less overall precision.
uint192 constant FIVE_PERCENT = 5e16; // {1} 0.05
uint192 constant TWENTY_PERCENT = 20e16; // {1} 0.2
uint192 constant TWENTY_FIVE_PERCENT = 25e16; // {1} 0.25
uint192 constant FORTY_FIVE_PERCENT = 45e16; // {1} 0.45
uint192 constant FIFTY_PERCENT = 50e16; // {1} 0.5
uint192 constant NINETY_FIVE_PERCENT = 95e16; // {1} 0.95
uint192 constant MAX_EXP = 6502287e18; // {1} (1000000/999999)^6502287 = ~666.6667
uint192 constant BASE = 999999e12; // {1} (999999/1000000)
uint192 constant ONE_POINT_FIVE = 150e16; // {1} 1.5
/**
* @title DutchTrade
* @notice Implements a wholesale dutch auction via a 4-piecewise falling-price mechansim.
* The overall idea is to handle 4 cases:
* 1. Price manipulation of the exchange rate up to 1000x (eg: via a read-only reentrancy)
* 2. Price movement of up to 50% during the auction
* 3. Typical case: no significant price movement; clearing price within expected range
* 4. No bots online; manual human doing bidding; additional time for tx clearing
*
* Case 1: Over the first 20% of the auction the price falls from ~1000x the best plausible
* price down to 1.5x the best plausible price in a geometric series.
* This period DOES NOT expect to receive a bid; it just defends against manipulated prices.
* If a bid occurs during this period, a violation is reported to the Broker.
* This is still safe for the protocol since other trades, with price discovery, can occur.
*
* Case 2: Over the next 20% of the auction the price falls from 1.5x the best plausible price
* to the best plausible price, linearly. No violation is reported if a bid occurs. This case
* exists to handle cases where prices change after the auction is started, naturally.
*
* Case 3: Over the next 50% of the auction the price falls from the best plausible price to the
* worst price, linearly. The worst price is further discounted by the maxTradeSlippage.
* This is the phase of the auction where bids will typically occur.
*
* Case 4: Lastly the price stays at the worst price for the final 5% of the auction to allow
* a bid to occur if no bots are online and the only bidders are humans.
*
* To bid:
* 1. Call `bidAmount()` view to check prices at various blocks.
* 2. Provide approval of sell tokens for precisely the `bidAmount()` desired
* 3. Wait until the desired block is reached (hopefully not in the first 20% of the auction)
* 4. Call bid()
*/
contract DutchTrade is ITrade {
using FixLib for uint192;
using SafeERC20 for IERC20Metadata;
TradeKind public constant KIND = TradeKind.DUTCH_AUCTION;
// solhint-disable-next-line var-name-mixedcase
uint48 public immutable ONE_BLOCK; // {s} 1 block based on network
BidType public bidType; // = BidType.NONE
TradeStatus public status; // reentrancy protection
IBroker public broker; // The Broker that cloned this contract into existence
ITrading public origin; // the address that initialized the contract
// === Auction ===
IERC20Metadata public sell;
IERC20Metadata public buy;
uint192 public sellAmount; // {sellTok}
// The auction runs from [startBlock, endTime], inclusive
uint256 public startBlock; // {block} when the dutch auction begins (one block after init())
uint256 public endBlock; // {block} when the dutch auction ends if no bids are received
uint48 public endTime; // {s} not used in this contract; needed on interface
uint192 public bestPrice; // {buyTok/sellTok} The best plausible price based on oracle data
uint192 public worstPrice; // {buyTok/sellTok} The worst plausible price based on oracle data
// === Bid ===
address public bidder;
// the bid amount is just whatever token balance is in the contract at settlement time
// This modifier both enforces the state-machine pattern and guards against reentrancy.
modifier stateTransition(TradeStatus begin, TradeStatus end) {
require(status == begin, "Invalid trade state");
status = TradeStatus.PENDING;
_;
assert(status == TradeStatus.PENDING);
status = end;
}
// === Auction Sizing Views ===
/// @return {qSellTok} The size of the lot being sold, in token quanta
function lot() public view returns (uint256) {
return sellAmount.shiftl_toUint(int8(sell.decimals()));
}
/// Calculates how much buy token is needed to purchase the lot at a particular block
/// @param blockNumber {block} The block number of the bid
/// @return {qBuyTok} The amount of buy tokens required to purchase the lot
function bidAmount(uint256 blockNumber) external view returns (uint256) {
return _bidAmount(_price(blockNumber));
}
// ==== Constructor ===
constructor() {
ONE_BLOCK = NetworkConfigLib.blocktime();
status = TradeStatus.CLOSED;
}
// === External ===
/// @param origin_ The Trader that originated the trade
/// @param sell_ The asset being sold by the protocol
/// @param buy_ The asset being bought by the protocol
/// @param sellAmount_ {qSellTok} The amount to sell in the auction, in token quanta
/// @param auctionLength {s} How many seconds the dutch auction should run for
function init(
ITrading origin_,
IAsset sell_,
IAsset buy_,
uint256 sellAmount_,
uint48 auctionLength,
TradePrices memory prices
) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) {
assert(
address(sell_) != address(0) &&
address(buy_) != address(0) &&
auctionLength >= 20 * ONE_BLOCK
); // misuse by caller
// Only start dutch auctions under well-defined prices
require(prices.sellLow != 0 && prices.sellHigh < FIX_MAX / 1000, "bad sell pricing");
require(prices.buyLow != 0 && prices.buyHigh < FIX_MAX / 1000, "bad buy pricing");
broker = IBroker(msg.sender);
origin = origin_;
sell = sell_.erc20();
buy = buy_.erc20();
require(sellAmount_ <= sell.balanceOf(address(this)), "unfunded trade");
sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok}
uint256 _startBlock = block.number + 1; // start in the next block
startBlock = _startBlock; // gas-saver
uint256 _endBlock = _startBlock + auctionLength / ONE_BLOCK; // FLOOR; endBlock is inclusive
endBlock = _endBlock; // gas-saver
endTime = uint48(block.timestamp + ONE_BLOCK * (_endBlock - _startBlock + 1));
// {buyTok/sellTok} = {UoA/sellTok} * {1} / {UoA/buyTok}
uint192 _worstPrice = prices.sellLow.mulDiv(
FIX_ONE - origin.maxTradeSlippage(),
prices.buyHigh,
FLOOR
);
uint192 _bestPrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage
assert(_worstPrice <= _bestPrice);
worstPrice = _worstPrice; // gas-saver
bestPrice = _bestPrice; // gas-saver
}
/// Bid for the auction lot at the current price; settle trade in protocol
/// @dev Caller must have provided approval
/// @return amountIn {qBuyTok} The quantity of tokens the bidder paid
function bid() external returns (uint256 amountIn) {
require(bidder == address(0), "bid already received");
// {buyTok/sellTok}
uint192 price = _price(block.number); // enforces auction ongoing
// {qBuyTok}
amountIn = _bidAmount(price);
// Mark bidder
bidder = msg.sender;
bidType = BidType.TRANSFER;
// status must begin OPEN
assert(status == TradeStatus.OPEN);
// reportViolation if auction cleared in geometric phase
if (price > bestPrice.mul(ONE_POINT_FIVE, CEIL)) {
broker.reportViolation();
}
// Transfer in buy tokens from bidder
buy.safeTransferFrom(msg.sender, address(this), amountIn);
// settle() in core protocol
origin.settleTrade(sell);
// confirm .settleTrade() succeeded and .settle() has been called
assert(status == TradeStatus.CLOSED);
}
/// Bid with callback for the auction lot at the current price; settle trade in protocol
/// Sold funds are sent back to the callee first via callee.dutchTradeCallback(...)
/// Balance of buy token must increase by bidAmount(current block) after callback
///
/// @dev Caller must implement IDutchTradeCallee
/// @param data {bytes} The data to pass to the callback
/// @return amountIn {qBuyTok} The quantity of tokens the bidder paid
function bidWithCallback(bytes calldata data) external returns (uint256 amountIn) {
require(bidder == address(0), "bid already received");
// {buyTok/sellTok}
uint192 price = _price(block.number); // enforces auction ongoing
// {qBuyTok}
amountIn = _bidAmount(price);
// Mark bidder
bidder = msg.sender;
bidType = BidType.CALLBACK;
// status must begin OPEN
assert(status == TradeStatus.OPEN);
// reportViolation if auction cleared in geometric phase
if (price > bestPrice.mul(ONE_POINT_FIVE, CEIL)) {
broker.reportViolation();
}
// Transfer sell tokens to bidder
sell.safeTransfer(bidder, lot()); // {qSellTok}
uint256 balanceBefore = buy.balanceOf(address(this)); // {qBuyTok}
IDutchTradeCallee(bidder).dutchTradeCallback(address(buy), amountIn, data);
require(
amountIn <= buy.balanceOf(address(this)) - balanceBefore,
"insufficient buy tokens"
);
// settle() in core protocol
origin.settleTrade(sell);
// confirm .settleTrade() succeeded and .settle() has been called
assert(status == TradeStatus.CLOSED);
}
/// Settle the auction, emptying the contract of balances
/// @return soldAmt {qSellTok} Token quantity sold by the protocol
/// @return boughtAmt {qBuyTok} Token quantity purchased by the protocol
function settle()
external
stateTransition(TradeStatus.OPEN, TradeStatus.CLOSED)
returns (uint256 soldAmt, uint256 boughtAmt)
{
require(msg.sender == address(origin), "only origin can settle");
require(bidder != address(0) || block.number > endBlock, "auction not over");
if (bidType == BidType.CALLBACK) {
soldAmt = lot(); // {qSellTok}
} else if (bidType == BidType.TRANSFER) {
soldAmt = lot(); // {qSellTok}
sell.safeTransfer(bidder, soldAmt); // {qSellTok}
}
// Transfer remaining balances back to origin
boughtAmt = buy.balanceOf(address(this)); // {qBuyTok}
buy.safeTransfer(address(origin), boughtAmt); // {qBuyTok}
sell.safeTransfer(address(origin), sell.balanceOf(address(this))); // {qSellTok}
}
/// Anyone can transfer any ERC20 back to the origin after the trade has been closed
/// @dev Escape hatch in case of accidentally transferred tokens after auction end
/// @custom:interaction CEI (and respects the state lock)
function transferToOriginAfterTradeComplete(IERC20Metadata erc20) external {
require(status == TradeStatus.CLOSED, "only after trade is closed");
erc20.safeTransfer(address(origin), erc20.balanceOf(address(this)));
}
/// @return true iff the trade can be settled.
// Guaranteed to be true some time after init(), until settle() is called
function canSettle() external view returns (bool) {
return status == TradeStatus.OPEN && (bidder != address(0) || block.number > endBlock);
}
// === Private ===
/// Return the price of the auction at a particular timestamp
/// @param blockNumber {block} The block number to get price for
/// @return {buyTok/sellTok}
function _price(uint256 blockNumber) private view returns (uint192) {
uint256 _startBlock = startBlock; // gas savings
uint256 _endBlock = endBlock; // gas savings
require(blockNumber >= _startBlock, "auction not started");
require(blockNumber <= _endBlock, "auction over");
/// Price Curve:
/// - first 20%: geometrically decrease the price from 1000x the bestPrice to 1.5x it
/// - next 25%: linearly decrease the price from 1.5x the bestPrice to 1x it
/// - next 50%: linearly decrease the price from bestPrice to worstPrice
/// - last 5%: constant at worstPrice
uint192 progression = divuu(blockNumber - _startBlock, _endBlock - _startBlock); // {1}
// Fast geometric decay -- 0%-20% of auction
if (progression < TWENTY_PERCENT) {
uint192 exp = MAX_EXP.mulDiv(TWENTY_PERCENT - progression, TWENTY_PERCENT, ROUND);
// bestPrice * ((1000000/999999) ^ exp) = bestPrice / ((999999/1000000) ^ exp)
// safe uint48 downcast: exp is at-most 6502287
// {buyTok/sellTok} = {buyTok/sellTok} / {1} ^ {1}
return bestPrice.mulDiv(ONE_POINT_FIVE, BASE.powu(uint48(exp.toUint(ROUND))), CEIL);
// this reverts for bestPrice >= 6.21654046e36 * FIX_ONE
} else if (progression < FORTY_FIVE_PERCENT) {
// First linear decay -- 20%-45% of auction
// 1.5x -> 1x the bestPrice
uint192 _bestPrice = bestPrice; // gas savings
// {buyTok/sellTok} = {buyTok/sellTok} * {1}
uint192 highPrice = _bestPrice.mul(ONE_POINT_FIVE, CEIL);
return
highPrice -
(highPrice - _bestPrice).mulDiv(progression - TWENTY_PERCENT, TWENTY_FIVE_PERCENT);
} else if (progression < NINETY_FIVE_PERCENT) {
// Second linear decay -- 45%-95% of auction
// bestPrice -> worstPrice
uint192 _bestPrice = bestPrice; // gas savings
// {buyTok/sellTok} = {buyTok/sellTok} * {1}
return
_bestPrice -
(_bestPrice - worstPrice).mulDiv(progression - FORTY_FIVE_PERCENT, FIFTY_PERCENT);
}
// Constant price -- 95%-100% of auction
return worstPrice;
}
/// Calculates how much buy token is needed to purchase the lot at a particular price
/// @param price {buyTok/sellTok}
/// @return {qBuyTok} The amount of buy tokens required to purchase the lot
function _bidAmount(uint192 price) public view returns (uint256) {
// {qBuyTok} = {sellTok} * {buyTok/sellTok} * {qBuyTok/buyTok}
return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL);
}
}
Address.sol 222 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @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
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 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
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
GnosisTrade.sol 244 lines
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "../../libraries/Allowance.sol";
import "../../libraries/Fixed.sol";
import "../../interfaces/IBroker.sol";
import "../../interfaces/IGnosis.sol";
import "../../interfaces/ITrade.sol";
// Modifications to this contract's state must only ever be made when status=PENDING!
/// Trade contract against the Gnosis EasyAuction mechanism
contract GnosisTrade is ITrade {
using FixLib for uint192;
using SafeERC20Upgradeable for IERC20Upgradeable;
// ==== Constants
TradeKind public constant KIND = TradeKind.BATCH_AUCTION;
uint256 public constant FEE_DENOMINATOR = 1000;
// Upper bound for the max number of orders we're happy to have the auction clear in;
// When we have good price information, this determines the minimum buy amount per order.
uint96 public constant MAX_ORDERS = 5000; // bounded to avoid going beyond block gas limit
// raw "/" for compile-time const
uint192 public constant DEFAULT_MIN_BID = FIX_ONE / 100; // {tok}
// ==== status: This contract's state-machine state. See TradeStatus enum, above
TradeStatus public status;
// ==== The rest of contract state is all parameters that are immutable after init()
// == Metadata
IGnosis public gnosis; // Gnosis Auction contract
uint256 public auctionId; // The Gnosis Auction ID returned by gnosis.initiateAuction()
IBroker public broker; // The Broker that cloned this contract into existence
// == Economic parameters
// This trade is on behalf of origin. Only origin may call settle(), and the `buy` tokens
// from this trade's acution will all eventually go to origin.
address public origin;
IERC20Metadata public sell; // address of token this trade is selling
IERC20Metadata public buy; // address of token this trade is buying
uint256 public initBal; // {qSellTok}, this trade's balance of `sell` when init() was called
uint192 public sellAmount; // {sellTok}, quantity of whole tokens being sold; dup with initBal
uint48 public endTime; // timestamp after which this trade's auction can be settled
uint192 public worstCasePrice; // {buyTok/sellTok}, the worst price we expect to get at Auction
// We expect Gnosis Auction either to meet or beat worstCasePrice, or to return the `sell`
// tokens. If we actually *get* a worse clearing that worstCasePrice, we consider it an error in
// our trading scheme and call broker.reportViolation()
// This modifier both enforces the state-machine pattern and guards against reentrancy.
modifier stateTransition(TradeStatus begin, TradeStatus end) {
require(status == begin, "Invalid trade state");
status = TradeStatus.PENDING;
_;
assert(status == TradeStatus.PENDING);
status = end;
}
constructor() {
status = TradeStatus.CLOSED;
}
/// Constructor function, can only be called once
/// @dev Expects sell tokens to already be present
/// @custom:interaction reentrancy-safe b/c state-locking
// checks:
// state is NOT_STARTED
// req.sellAmount <= our balance of sell tokens < 2**96
// req.minBuyAmount < 2**96
// effects:
// state' is OPEN
// correctly sets all Metadata and Economic parameters of this contract
//
// actions:
// increases the `req.sell` allowance for `gnosis` by the amount needed to fund the auction
// calls gnosis.initiateAuction(...) to launch the requested auction.
function init(
IBroker broker_,
address origin_,
IGnosis gnosis_,
uint48 batchAuctionLength,
TradeRequest calldata req
) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) {
require(req.sellAmount <= type(uint96).max, "sellAmount too large");
require(req.minBuyAmount <= type(uint96).max, "minBuyAmount too large");
sell = req.sell.erc20();
buy = req.buy.erc20();
initBal = sell.balanceOf(address(this)); // {qSellTok}
sellAmount = shiftl_toFix(initBal, -int8(sell.decimals())); // {sellTok}
require(initBal <= type(uint96).max, "initBal too large");
require(initBal >= req.sellAmount, "unfunded trade");
assert(origin_ != address(0));
broker = broker_;
origin = origin_;
gnosis = gnosis_;
endTime = uint48(block.timestamp) + batchAuctionLength;
// {buyTok/sellTok}
worstCasePrice = shiftl_toFix(req.minBuyAmount, -int8(buy.decimals())).div(
shiftl_toFix(req.sellAmount, -int8(sell.decimals()))
);
// Downsize our sell amount to adjust for fee
// {qSellTok} = {qSellTok} * {1} / {1}
uint96 _sellAmount = uint96(
_divrnd(
req.sellAmount * FEE_DENOMINATOR,
FEE_DENOMINATOR + gnosis.feeNumerator(),
FLOOR
)
);
// Don't decrease minBuyAmount even if fees are in effect. The fee is part of the slippage
uint96 minBuyAmount = uint96(Math.max(1, req.minBuyAmount)); // Safe downcast; require'd
uint256 minBuyAmtPerOrder = Math.max(
minBuyAmount / MAX_ORDERS,
DEFAULT_MIN_BID.shiftl_toUint(int8(buy.decimals()))
);
// Gnosis EasyAuction requires minBuyAmtPerOrder > 0
// untestable:
// Value will always be at least 1. Handled previously in the calling contracts.
if (minBuyAmtPerOrder == 0) minBuyAmtPerOrder = 1;
// == Interactions ==
// Set allowance via custom approval -- first sets allowance to 0, then sets allowance
// to either the requested amount or the maximum possible amount, if that fails.
//
// Context: wcUSDCv3 has a non-standard approve() function that reverts if the approve
// amount is > 0 and < type(uint256).max.
AllowanceLib.safeApproveFallbackToMax(address(sell), address(gnosis), initBal);
auctionId = gnosis.initiateAuction(
sell,
buy,
endTime,
endTime,
_sellAmount,
minBuyAmount,
minBuyAmtPerOrder,
0,
false,
address(0),
new bytes(0)
);
}
/// Settle trade, transfer tokens to trader, and report bad trade if needed
/// @custom:interaction reentrancy-safe b/c state-locking
// checks:
// state is OPEN
// caller is `origin`
// now >= endTime
// actions:
// (if not already called) call gnosis.settleAuction(auctionID), which:
// settles the Gnosis Auction
// transfers the resulting tokens back to this address
// if the auction's clearing price was below what we assert it should be,
// then broker.reportViolation()
// transfer all balancess of `buy` and `sell` at this address to `origin`
// effects:
// state' is CLOSED
function settle()
external
stateTransition(TradeStatus.OPEN, TradeStatus.CLOSED)
returns (uint256 soldAmt, uint256 boughtAmt)
{
require(msg.sender == origin, "only origin can settle");
// Optionally process settlement of the auction in Gnosis
if (!isAuctionCleared()) {
// By design, we don't rely on this return value at all, just the
// "cleared" state of the auction, and the token balances this contract owns.
// slither-disable-next-line unused-return
gnosis.settleAuction(auctionId);
assert(isAuctionCleared());
}
// At this point we know the auction has cleared
// Transfer balances to origin
uint256 sellBal = sell.balanceOf(address(this));
// As raised in C4's review, this balance can be manupulated by a frontrunner
// It won't really affect the outcome of the trade, as protocol still gets paid
// and it just gets a better clearing price than expected.
// Fixing it would require some complex logic, as SimpleAuction does not expose
// the amount of tokens bought by the auction after the tokens are settled.
// So we will live with this for now. Worst case, there will be a mismatch between
// the trades recorded by the IDO contracts and on our side.
boughtAmt = buy.balanceOf(address(this));
if (sellBal > 0) IERC20Upgradeable(address(sell)).safeTransfer(origin, sellBal);
if (boughtAmt > 0) IERC20Upgradeable(address(buy)).safeTransfer(origin, boughtAmt);
// Check clearing prices
if (sellBal < initBal) {
soldAmt = initBal - sellBal;
// Gnosis rounds defensively in the buy token; we should not consider it a violation
uint256 adjustedSoldAmt = Math.max(soldAmt, 1);
uint256 adjustedBuyAmt = boughtAmt + 1;
// {buyTok/sellTok}
uint192 clearingPrice = shiftl_toFix(adjustedBuyAmt, -int8(buy.decimals())).div(
shiftl_toFix(adjustedSoldAmt, -int8(sell.decimals()))
);
if (clearingPrice.lt(worstCasePrice)) {
broker.reportViolation();
}
}
}
/// Anyone can transfer any ERC20 back to the origin after the trade has been closed
/// @dev Escape hatch in case trading partner freezes up, or other unexpected events
/// @custom:interaction CEI (and respects the state lock)
function transferToOriginAfterTradeComplete(IERC20 erc20) external {
require(status == TradeStatus.CLOSED, "only after trade is closed");
IERC20Upgradeable(address(erc20)).safeTransfer(origin, erc20.balanceOf(address(this)));
}
/// @return True if the trade can be settled.
// Guaranteed to be true some time after init(), until settle() is called
function canSettle() external view returns (bool) {
return status == TradeStatus.OPEN && endTime <= block.timestamp;
}
// === Private ===
function isAuctionCleared() private view returns (bool) {
GnosisAuctionData memory data = gnosis.auctionData(auctionId);
return data.clearingPriceOrder != bytes32(0);
}
}
Multicall.sol 24 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Multicall.sol)
pragma solidity ^0.8.0;
import "./Address.sol";
/**
* @dev Provides a function to batch together multiple calls in a single external call.
*
* _Available since v4.1._
*/
abstract contract Multicall {
/**
* @dev Receives and executes a batch of function calls on this contract.
*/
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = Address.functionDelegateCall(address(this), data[i]);
}
return results;
}
}
Math.sol 226 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. It the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`.
// We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`.
// This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`.
// Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a
// good first aproximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1;
uint256 x = a;
if (x >> 128 > 0) {
x >>= 128;
result <<= 64;
}
if (x >> 64 > 0) {
x >>= 64;
result <<= 32;
}
if (x >> 32 > 0) {
x >>= 32;
result <<= 16;
}
if (x >> 16 > 0) {
x >>= 16;
result <<= 8;
}
if (x >> 8 > 0) {
x >>= 8;
result <<= 4;
}
if (x >> 4 > 0) {
x >>= 4;
result <<= 2;
}
if (x >> 2 > 0) {
result <<= 1;
}
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
uint256 result = sqrt(a);
if (rounding == Rounding.Up && result * result < a) {
result += 1;
}
return result;
}
}
IERC20.sol 82 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the 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 `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, 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 `from` to `to` 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 from,
address to,
uint256 amount
) external returns (bool);
}
SafeERC20.sol 116 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.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));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @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");
}
}
}
AddressUpgradeable.sol 195 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @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
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 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 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
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
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);
}
AggregatorV3Interface.sol 32 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);
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
);
}
IERC20Upgradeable.sol 82 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the 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 `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, 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 `from` to `to` 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 from,
address to,
uint256 amount
) external returns (bool);
}
draft-IERC20Permit.sol 60 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
IAccessControlUpgradeable.sol 88 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControlUpgradeable {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
IVotesUpgradeable.sol 61 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (governance/utils/IVotes.sol)
pragma solidity ^0.8.0;
/**
* @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts.
*
* _Available since v4.5._
*/
interface IVotesUpgradeable {
/**
* @dev Emitted when an account changes their delegate.
*/
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
/**
* @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of votes.
*/
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
/**
* @dev Returns the current amount of votes that `account` has.
*/
function getVotes(address account) external view returns (uint256);
/**
* @dev Returns the amount of votes that `account` had at the end of a past block (`blockNumber`).
*/
function getPastVotes(address account, uint256 blockNumber) external view returns (uint256);
/**
* @dev Returns the total supply of votes available at the end of a past block (`blockNumber`).
*
* NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes.
* Votes that have not been delegated are still part of total supply, even though they would not participate in a
* vote.
*/
function getPastTotalSupply(uint256 blockNumber) external view returns (uint256);
/**
* @dev Returns the delegate that `account` has chosen.
*/
function delegates(address account) external view returns (address);
/**
* @dev Delegates votes from the sender to `delegatee`.
*/
function delegate(address delegatee) external;
/**
* @dev Delegates votes from signer to `delegatee`.
*/
function delegateBySig(
address delegatee,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
SafeERC20Upgradeable.sol 116 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
import "../extensions/draft-IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable {
using AddressUpgradeable for address;
function safeTransfer(
IERC20Upgradeable token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20Upgradeable 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(
IERC20Upgradeable 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(
IERC20Upgradeable 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(
IERC20Upgradeable 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));
}
}
function safePermit(
IERC20PermitUpgradeable token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @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(IERC20Upgradeable 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");
}
}
}
IERC20MetadataUpgradeable.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
/**
* @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);
}
draft-IERC20PermitUpgradeable.sol 60 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20PermitUpgradeable {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
Write Contract 5 functions
These functions modify contract state and require a wallet transaction to execute.
claimRewards 0xef5cfb8c
address rToken
multicall 0xac9650d8
bytes[] data
returns: bytes[]
nextRecollateralizationAuction 0xb6f86321
address bm
uint8 kind
returns: bool, address, address, uint256
revenueOverview 0x27b4b4bf
address revenueTrader
returns: address[], bool[], uint256[], uint256[], uint256[], uint256[]
runRevenueAuctions 0xf2c11da5
address revenueTrader
address[] toSettle
address[] toStart
uint8[] kinds
Recent Transactions
This address has 1 on-chain transactions, but only 1.2% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →