Address Contract Partially Verified
Address
0x4A1dbC839b4eBAD8dB9058e856E90cb41DFfce1F
Balance
0 ETH
Nonce
1
Code Size
9845 bytes
Creator
0xaB066655...5483 at tx 0x7eaf94cd...fe236a
Indexed Transactions
0 (1 on-chain, 0.7% indexed)
Contract Bytecode
9845 bytes
0x734a1dbc839b4ebad8db9058e856e90cb41dffce1f30146080604052600436106100565760003560e01c8063143d9d381461005b5780632c9745af1461007d57806383dc420b1461009d578063c922066a146100bd575b600080fd5b81801561006757600080fd5b5061007b6100763660046120a5565b6100dd565b005b81801561008957600080fd5b5061007b610098366004612251565b610298565b8180156100a957600080fd5b5061007b6100b83660046120a5565b6105dc565b8180156100c957600080fd5b5061007b6100d8366004612235565b610be2565b426202a3008360000151606001510110156101135760405162461bcd60e51b815260040161010a906124e5565b60405180910390fd5b6000610135836020015184600001516102200151856000015160a00151610f18565b90506000610144848385611036565b905060008085600001516040015161015e57600083610162565b8260005b8751610180015191935091506001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214801561019b57508551608001515b156102205785602001516001600160a01b031663022c0d9f838330886040518563ffffffff1660e01b81526004016101d694939291906125a0565b600060405180830381600087803b1580156101f057600080fd5b505af1158015610204573d6000803e3d6000fd5b5050505061021b86600001516101a0015184611515565b610290565b602086015186516101a0015160405163022c0d9f60e01b81526001600160a01b039092169163022c0d9f9161025d91869186918a906004016125a0565b600060405180830381600087803b15801561027757600080fd5b505af115801561028b573d6000803e3d6000fd5b505050505b505050505050565b426202a30085606001350110156102c15760405162461bcd60e51b815260040161010a906124e5565b60008060006102d1878786611658565b919450925090506102ea6102208801610200890161204b565b80156102f557508015155b156104b5576000610310878961022001358a60a00135610f18565b905083158015906103215750816001145b156103e55760007380246ac7a8f51d61856a1f62b2df8ad0fcee24d263d0b0a9bc896103556101808d016101608e01612013565b888d6101c001358c886040518763ffffffff1660e01b815260040161037f9695949392919061242d565b604080518083038186803b15801561039657600080fd5b505af41580156103aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103ce91906122ec565b90955090506103dd84826118b0565b9350506104b3565b82158015906103f45750816002145b156104b35760007380246ac7a8f51d61856a1f62b2df8ad0fcee24d263adf5f8f3896104286101a08d016101808e01612013565b878d6101e001358c886040518763ffffffff1660e01b81526004016104529695949392919061242d565b604080518083038186803b15801561046957600080fd5b505af415801561047d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a191906122ec565b945090506104af85826118b0565b9450505b505b82158015906104c357508115155b15610593577380246ac7a8f51d61856a1f62b2df8ad0fcee24d2637a528bd2876104f56101c08b016101a08c01612013565b6105076101808c016101608d01612013565b6105196101a08d016101808e01612013565b88886040518763ffffffff1660e01b815260040161053c969594939291906123f3565b60606040518083038186803b15801561055457600080fd5b505af4158015610568573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058c919061230f565b5090935091505b6105d36105a86101c089016101a08a01612013565b6105ba6101808a016101608b01612013565b6105cc6101a08b016101808c01612013565b86866118f7565b50505050505050565b426202a3008360000151606001510110156106095760405162461bcd60e51b815260040161010a906124e5565b81516101608101516101208201516102408301516101a090930151604051632a359a6d60e21b815260009473e92b1734fc37f8acc950d0d13a414753729587309463a8d669b4946106629489949293919260040161255d565b60206040518083038186803b15801561067a57600080fd5b505af415801561068e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106b291906122d4565b905060006106d6846020015185600001516102200151866000015160a00151610f18565b9050600080600080876000015160400151905060008089602001516001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401604080518083038186803b15801561072857600080fd5b505afa15801561073c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061076091906122a2565b915091506107856001846107745782610776565b835b6001600160701b031690611919565b93505050600088602001516001600160a01b0316637dc0d1d06040518163ffffffff1660e01b815260040160206040518083038186803b1580156107c857600080fd5b505afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610800919061202f565b9050600089602001516001600160a01b03166354cf2aeb6040518163ffffffff1660e01b815260040160206040518083038186803b15801561084157600080fd5b505afa158015610855573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061087991906122d4565b8a516101400151604051636d55a5b760e11b81529192506001600160a01b0384169163daab4b6e916108b391879186918d90600401612496565b604080518083038186803b1580156108ca57600080fd5b505afa1580156108de573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061090291906122ec565b90965094506000848611156109ba578a51610140015161092c906109268b88611949565b9061199e565b60405163a7d2087760e01b81529091506001600160a01b0384169063a7d208779061096190879086908a908e90600401612496565b604080518083038186803b15801561097857600080fd5b505afa15801561098c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109b091906122ec565b90975095506109c6565b50895161014001519450875b868110156109e65760405162461bcd60e51b815260040161010a906124c7565b86891115610a69578a5161016001516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2148015610a2257508a51608001515b15610a46578a516101a00151610a4190610a3c8b8a611919565b611515565b610a69565b8a516101608101516101a090910151610a699190610a648c8b611919565b6119ce565b610a818b6000015161016001518c60200151896119ce565b5050506040880151610a9890849061ffff16611919565b92506000808215610aab57849150610aae565b50835b895161018001516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2148015610ae257508951608001515b15610b675789602001516001600160a01b031663022c0d9f8383308b6040518563ffffffff1660e01b8152600401610b1d94939291906125a0565b600060405180830381600087803b158015610b3757600080fd5b505af1158015610b4b573d6000803e3d6000fd5b50505050610b628a600001516101a0015186611515565b61028b565b60208a01518a516101a0015160405163022c0d9f60e01b81526001600160a01b039092169163022c0d9f91610ba491869186918d906004016125a0565b600060405180830381600087803b158015610bbe57600080fd5b505af1158015610bd2573d6000803e3d6000fd5b5050505050505050505050505050565b426202a3008260600135011015610c0b5760405162461bcd60e51b815260040161010a906124e5565b6000610c39610c2261018084016101608501612013565b610c346101a085016101808601612013565b611b19565b509050610c4c81828461010001356119ce565b60008080610c6060a086016080870161204b565b8015610cd0575073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2610c8e61018087016101608801612013565b6001600160a01b03161480610cd0575073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2610cc56101a087016101808801612013565b6001600160a01b0316145b15610e47576000734d6740c0224e4ae4d9cb5b4900435d768dbff20463cf58beed610d0361018089016101608a01612013565b610d156101a08a016101808b01612013565b8873c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2610d3d6101c08d016101a08e01612013565b610d476000611bed565b6040518763ffffffff1660e01b8152600401610d68969594939291906123b8565b60806040518083038186803b158015610d8057600080fd5b505af4158015610d94573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610db89190612067565b91965094509250905080610e415773e92b1734fc37f8acc950d0d13a41475372958730630966c1d6610df26101c089016101a08a01612013565b866040518363ffffffff1660e01b8152600401610e1092919061247d565b60006040518083038186803b158015610e2857600080fd5b505af4158015610e3c573d6000803e3d6000fd5b505050505b50610edb565b6001600160a01b0384166389afcb44610e686101c088016101a08901612013565b6040518263ffffffff1660e01b8152600401610e84919061238a565b6040805180830381600087803b158015610e9d57600080fd5b505af1158015610eb1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ed591906122ec565b90925090505b8461012001358210158015610ef557508461014001358110155b610f115760405162461bcd60e51b815260040161010a9061253f565b5050505050565b60606000846001600160a01b0316637dc0d1d06040518163ffffffff1660e01b815260040160206040518083038186803b158015610f5557600080fd5b505afa158015610f69573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f8d919061202f565b6001600160a01b0316636a9150fe85856040518363ffffffff1660e01b8152600401610fba929190612592565b60206040518083038186803b158015610fd257600080fd5b505afa158015610fe6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061100a91906122d4565b90508060405160200161101d9190612589565b6040516020818303038152906040529150509392505050565b60008060008086602001516001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401604080518083038186803b15801561107857600080fd5b505afa15801561108c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b091906122a2565b915091506110cc60018860000151604001516107745782610776565b92505050600085602001516001600160a01b0316637dc0d1d06040518163ffffffff1660e01b815260040160206040518083038186803b15801561110f57600080fd5b505afa158015611123573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611147919061202f565b9050600086602001516001600160a01b03166354cf2aeb6040518163ffffffff1660e01b815260040160206040518083038186803b15801561118857600080fd5b505afa15801561119c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111c091906122d4565b87516101608101516101208201516102408301516101a090930151604051632a359a6d60e21b815294955060009473e92b1734fc37f8acc950d0d13a414753729587309463a8d669b49461121e948d9491939092919060040161255d565b60206040518083038186803b15801561123657600080fd5b505af415801561124a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061126e91906122d4565b885160400151909150611300576040516304a8fad760e31b81526001600160a01b03841690632547d6b8906112ab90859085908c906004016125cd565b60206040518083038186803b1580156112c357600080fd5b505afa1580156112d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112fb91906122d4565b611380565b60405163c4109d2560e01b81526001600160a01b0384169063c4109d259061133090859085908c906004016125cd565b60206040518083038186803b15801561134857600080fd5b505afa15801561135c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061138091906122d4565b94506000848611156114b257885161014001516113a99087906113a39088611949565b90611ec5565b8951604090810151905163a7d2087760e01b815291925083916001600160a01b0387169163a7d20877916113e6919088908b908f90600401612496565b604080518083038186803b1580156113fd57600080fd5b505afa158015611411573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061143591906122ec565b8b5161016001519098509093506001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214801561146f57508951608001515b1561148e5789516101a0015161148990610a3c8386611919565b6114ac565b89516101608101516101a0909101516114ac9190610a648487611919565b506114bb565b50875161014001515b60408901516114cf90879061ffff16611919565b9550808610156114f15760405162461bcd60e51b815260040161010a90612521565b611509896000015161016001518a60200151846119ce565b50505050509392505050565b604051632e1a7d4d60e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9061154c908490600401612589565b600060405180830381600087803b15801561156657600080fd5b505af115801561157a573d6000803e3d6000fd5b505050506000826001600160a01b0316826115956000611bed565b906040516115a290612387565b600060405180830381858888f193505050503d80600081146115e0576040519150601f19603f3d011682016040523d82523d6000602084013e6115e5565b606091505b5050905080611653576040516304b360eb60e11b815273e92b1734fc37f8acc950d0d13a4147537295873090630966c1d690611627908690869060040161247d565b60006040518083038186803b15801561163f57600080fd5b505af41580156105d3573d6000803e3d6000fd5b505050565b600080808073e92b1734fc37f8acc950d0d13a4147537295873063a8d669b48661168a6101808b016101608c01612013565b6101208b01356102408c01356116a86101c08e016101a08f01612013565b6040518663ffffffff1660e01b81526004016116c895949392919061255d565b60206040518083038186803b1580156116e057600080fd5b505af41580156116f4573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061171891906122d4565b9050600073e92b1734fc37f8acc950d0d13a4147537295873063a8d669b4876117496101a08c016101808d01612013565b8b61014001358c61026001358d6101a00160208101906117699190612013565b6040518663ffffffff1660e01b815260040161178995949392919061255d565b60206040518083038186803b1580156117a157600080fd5b505af41580156117b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117d991906122d4565b90507380246ac7a8f51d61856a1f62b2df8ad0fcee24d2637a528bd2886118086101c08c016101a08d01612013565b61181a6101808d016101608e01612013565b61182c6101a08e016101808f01612013565b87876040518763ffffffff1660e01b815260040161184f969594939291906123f3565b60606040518083038186803b15801561186757600080fd5b505af415801561187b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061189f919061230f565b919a90995090975095505050505050565b808201828110156118f1576040805162461bcd60e51b81526020600480830191909152602482015263534d344560e01b604482015290519081900360640190fd5b92915050565b8115611908576119088486846119ce565b8015610f1157610f118386836119ce565b600061194283836040518060400160405280600481526020016329a6989960e11b815250611f15565b9392505050565b60008115806119645750508082028282828161196157fe5b04145b6118f1576040805162461bcd60e51b81526020600480830191909152602482015263534d324160e01b604482015290519081900360640190fd5b60006119aa8383611ec5565b90506119b68282611949565b83146118f1576119c78160016118b0565b90506118f1565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b60208310611a4a5780518252601f199092019160209182019101611a2b565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611aac576040519150601f19603f3d011682016040523d82523d6000602084013e611ab1565b606091505b5091509150818015611adf575080511580611adf5750808060200190516020811015611adc57600080fd5b50515b610f11576040805162461bcd60e51b815260206004808301919091526024820152635448303560e01b604482015290519081900360640190fd5b60405163e6a4390560e01b8152600090819073c480b33ee5229de3fbdfad1d2dcd3f3bad0c56c69063e6a4390590611b57908790879060040161239e565b60206040518083038186803b158015611b6f57600080fd5b505afa158015611b83573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ba7919061202f565b91506001600160a01b038216611bcf5760405162461bcd60e51b815260040161010a90612503565b826001600160a01b0316846001600160a01b03161190509250929050565b60006001600160a01b038216611c065750612710611ec0565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b0383161415611c345750617918611ec0565b73a0b86991c6218b36c1d19d4a2e9eb0ce3606eb486001600160a01b0383161415611c62575061a410611ec0565b73dac17f958d2ee523a2206206994597c13d831ec76001600160a01b0383161415611c915750620101d0611ec0565b732260fac5e5542a773aa44fbcfedf7c193bc2c5996001600160a01b0383161415611cbf57506184d0611ec0565b734e3fbd56cd56c3e72c1403e103b45db9da5b9d2b6001600160a01b0383161415611ced5750617918611ec0565b736b3595068778dd592e39a122f4f5a5cf09c90fe26001600160a01b0383161415611d1b5750617918611ec0565b73ae7ab96520de3a18e5e111b5eaab095312d7fe846001600160a01b0383161415611d4a5750620109a0611ec0565b737f39c581f595b53c5cb19bd0b3f8da6c935e2ca06001600160a01b0383161415611d785750617918611ec0565b73d33526068d116ce69f19a9ee46f0bd304f21a51f6001600160a01b0383161415611da65750617918611ec0565b7348c3399719b582dd63eb5aadf12a40b4c3f52fa26001600160a01b0383161415611dd45750619c40611ec0565b735a98fcbea516cf06857215779fd812ca3bef1b326001600160a01b0383161415611e03575062024608611ec0565b739f8f72aa9304c8b593d555f12ef6589cc3a579a26001600160a01b0383161415611e3157506184d0611ec0565b731f9840a85d5af5bf1d1762f925bdaddc4201f9846001600160a01b0383161415611e5f5750619088611ec0565b73514910771af9ca656af840dff83e8264ecf986ca6001600160a01b0383161415611e8d5750617d00611ec0565b733c3a81e81dc49a522a592e7622a7e711c06bf3546001600160a01b0383161415611ebb57506184d0611ec0565b5061ea605b919050565b6000808211611f04576040805162461bcd60e51b81526020600480830191909152602482015263534d343360e01b604482015290519081900360640190fd5b818381611f0d57fe5b049392505050565b8183038184821115611fa55760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015611f6a578181015183820152602001611f52565b50505050905090810190601f168015611f975780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b509392505050565b8035611ec081612619565b8035611ec081612631565b803560058110611ec057600080fd5b60006102808284031215611fe4578081fd5b50919050565b80516001600160701b0381168114611ec057600080fd5b803561ffff81168114611ec057600080fd5b600060208284031215612024578081fd5b813561194281612619565b600060208284031215612040578081fd5b815161194281612619565b60006020828403121561205c578081fd5b813561194281612631565b6000806000806080858703121561207c578283fd5b845161208781612631565b60208601516040870151606090970151919890975090945092505050565b6000808284036102e08112156120b9578283fd5b6102c0808212156120c8578384fd5b6040516060810181811067ffffffffffffffff821117156120e557fe5b604052610280808412156120f7578586fd5b612100816125f5565b93508635845261211260208801611fc3565b602085015261212360408801611fb8565b60408501526060870135606085015261213e60808801611fb8565b608085015260a087013560a085015260c087013560c085015260e087013560e0850152610100808801358186015250610120808801358186015250610140808801358186015250610160612193818901611fad565b908501526101806121a5888201611fad565b908501526101a06121b7888201611fad565b908501526101c087810135908501526101e080880135908501526102006121df818901611fb8565b90850152610220878101359085015261024080880135908501526102608088013590850152838252612212818801611fad565b6020830152506122256102a08701612001565b6040820152969401359450505050565b60006102808284031215612247578081fd5b6119428383611fd2565b6000806000806102e08587031215612267578384fd5b6122718686611fd2565b935061028085013561228281612619565b92506122916102a08601612001565b939692955092936102c00135925050565b600080604083850312156122b4578182fd5b6122bd83611fea565b91506122cb60208401611fea565b90509250929050565b6000602082840312156122e5578081fd5b5051919050565b600080604083850312156122fe578182fd5b505080516020909101519092909150565b600080600060608486031215612323578081fd5b8351925060208401519150604084015190509250925092565b60008151808452815b8181101561236157602081850181015186830182015201612345565b818111156123725782602083870101525b50601f01601f19169290920160200192915050565b90565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b039687168152948616602086015292851660408501529084166060840152909216608082015260a081019190915260c00190565b6001600160a01b0396871681529486166020860152928516604085015293166060830152608082019290925260a081019190915260c00190565b6001600160a01b03878116825286166020820152604081018590526060810184905261ffff8316608082015260c060a082018190526000906124719083018461233c565b98975050505050505050565b6001600160a01b03929092168252602082015260400190565b60008515158252846020830152836040830152608060608301526124bd608083018461233c565b9695505050505050565b60208082526004908201526308a9060760e31b604082015260600190565b60208082526004908201526311520c0d60e21b604082015260600190565b6020808252600490820152634f53313760e01b604082015260600190565b6020808252600490820152634548333760e01b604082015260600190565b6020808252600490820152634548303360e01b604082015260600190565b9485526001600160a01b0393841660208601526040850192909252606084015216608082015260a00190565b90815260200190565b918252602082015260400190565b600085825284602083015260018060a01b0384166040830152608060608301526124bd608083018461233c565b6000848252836020830152606060408301526125ec606083018461233c565b95945050505050565b60405181810167ffffffffffffffff8111828210171561261157fe5b604052919050565b6001600160a01b038116811461262e57600080fd5b50565b801515811461262e57600080fdfea26469706673582212200e33b0a58fbac3fae6360e7de57004874dde2077e2f0efe9da3f742775d1e89b64736f6c63430007060033
Verified Source Code Partial Match
Compiler: v0.7.6+commit.7338295f
EVM: istanbul
Optimization: Yes (200 runs)
IERC20.sol 29 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
IReserves.sol 12 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
interface IReserves {
function getReserves() external view returns (uint112 reserve0, uint112 reserve1);
function getFees() external view returns (uint256 fee0, uint256 fee1);
}
ITwapERC20.sol 28 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
import './IERC20.sol';
interface ITwapERC20 is IERC20 {
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint256);
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
}
ITwapFactory.sol 37 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
interface ITwapFactory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint256);
event OwnerSet(address owner);
function owner() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint256) external view returns (address pair);
function allPairsLength() external view returns (uint256);
function createPair(address tokenA, address tokenB, address oracle, address trader) external returns (address pair);
function setOwner(address) external;
function setMintFee(address tokenA, address tokenB, uint256 fee) external;
function setBurnFee(address tokenA, address tokenB, uint256 fee) external;
function setSwapFee(address tokenA, address tokenB, uint256 fee) external;
function setOracle(address tokenA, address tokenB, address oracle) external;
function setTrader(address tokenA, address tokenB, address trader) external;
function collect(address tokenA, address tokenB, address to) external;
function withdraw(address tokenA, address tokenB, uint256 amount, address to) external;
}
ITwapOracle.sol 85 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
interface ITwapOracle {
event OwnerSet(address owner);
event UniswapPairSet(address uniswapPair);
function decimalsConverter() external view returns (int256);
function xDecimals() external view returns (uint8);
function yDecimals() external view returns (uint8);
function owner() external view returns (address);
function uniswapPair() external view returns (address);
function getPriceInfo() external view returns (uint256 priceAccumulator, uint256 priceTimestamp);
function getSpotPrice() external view returns (uint256);
function getAveragePrice(uint256 priceAccumulator, uint256 priceTimestamp) external view returns (uint256);
function setOwner(address _owner) external;
function setUniswapPair(address _uniswapPair) external;
function tradeX(
uint256 xAfter,
uint256 xBefore,
uint256 yBefore,
bytes calldata data
) external view returns (uint256 yAfter);
function tradeY(
uint256 yAfter,
uint256 yBefore,
uint256 xBefore,
bytes calldata data
) external view returns (uint256 xAfter);
function depositTradeXIn(
uint256 xLeft,
uint256 xBefore,
uint256 yBefore,
bytes calldata data
) external view returns (uint256 xIn);
function depositTradeYIn(
uint256 yLeft,
uint256 yBefore,
uint256 xBefore,
bytes calldata data
) external view returns (uint256 yIn);
function getSwapAmount0Out(
uint256 swapFee,
uint256 amount1In,
bytes calldata data
) external view returns (uint256 amount0Out);
function getSwapAmount1Out(
uint256 swapFee,
uint256 amount0In,
bytes calldata data
) external view returns (uint256 amount1Out);
function getSwapAmountInMaxOut(
bool inverse,
uint256 swapFee,
uint256 _amountOut,
bytes calldata data
) external view returns (uint256 amountIn, uint256 amountOut);
function getSwapAmountInMinOut(
bool inverse,
uint256 swapFee,
uint256 _amountOut,
bytes calldata data
) external view returns (uint256 amountIn, uint256 amountOut);
}
ITwapPair.sol 79 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
import './ITwapERC20.sol';
import './IReserves.sol';
interface ITwapPair is ITwapERC20, IReserves {
event Mint(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 liquidityOut, address indexed to);
event Burn(address indexed sender, uint256 amount0Out, uint256 amount1Out, uint256 liquidityIn, address indexed to);
event Swap(
address indexed sender,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out,
address indexed to
);
event SetMintFee(uint256 fee);
event SetBurnFee(uint256 fee);
event SetSwapFee(uint256 fee);
event SetOracle(address account);
event SetTrader(address trader);
function MINIMUM_LIQUIDITY() external pure returns (uint256);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function oracle() external view returns (address);
function trader() external view returns (address);
function mintFee() external view returns (uint256);
function setMintFee(uint256 fee) external;
function mint(address to) external returns (uint256 liquidity);
function burnFee() external view returns (uint256);
function setBurnFee(uint256 fee) external;
function burn(address to) external returns (uint256 amount0, uint256 amount1);
function swapFee() external view returns (uint256);
function setSwapFee(uint256 fee) external;
function setOracle(address account) external;
function setTrader(address account) external;
function collect(address to) external;
function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
function sync() external;
function initialize(address _token0, address _token1, address _oracle, address _trader) external;
function getSwapAmount0In(uint256 amount1Out, bytes calldata data) external view returns (uint256 swapAmount0In);
function getSwapAmount1In(uint256 amount0Out, bytes calldata data) external view returns (uint256 swapAmount1In);
function getSwapAmount0Out(uint256 amount1In, bytes calldata data) external view returns (uint256 swapAmount0Out);
function getSwapAmount1Out(uint256 amount0In, bytes calldata data) external view returns (uint256 swapAmount1Out);
function getDepositAmount0In(uint256 amount0, bytes calldata data) external view returns (uint256 depositAmount0In);
function getDepositAmount1In(uint256 amount1, bytes calldata data) external view returns (uint256 depositAmount1In);
}
IWETH.sol 14 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
interface IWETH {
function deposit() external payable;
function transfer(address to, uint256 value) external returns (bool);
function withdraw(uint256) external;
}
AddLiquidity.sol 130 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
import './TransferHelper.sol';
import './SafeMath.sol';
import './Math.sol';
import '../interfaces/ITwapPair.sol';
import '../interfaces/ITwapOracle.sol';
library AddLiquidity {
using SafeMath for uint256;
function addLiquidity(
address pair,
uint256 amount0Desired,
uint256 amount1Desired
) internal view returns (uint256 amount0, uint256 amount1, uint256 swapToken) {
if (amount0Desired == 0 || amount1Desired == 0) {
if (amount0Desired > 0) {
swapToken = 1;
} else if (amount1Desired > 0) {
swapToken = 2;
}
return (0, 0, swapToken);
}
(uint256 reserve0, uint256 reserve1) = ITwapPair(pair).getReserves();
if (reserve0 == 0 && reserve1 == 0) {
(amount0, amount1) = (amount0Desired, amount1Desired);
} else {
require(reserve0 > 0 && reserve1 > 0, 'AL07');
uint256 amount1Optimal = amount0Desired.mul(reserve1) / reserve0;
if (amount1Optimal <= amount1Desired) {
swapToken = 2;
(amount0, amount1) = (amount0Desired, amount1Optimal);
} else {
uint256 amount0Optimal = amount1Desired.mul(reserve0) / reserve1;
assert(amount0Optimal <= amount0Desired);
swapToken = 1;
(amount0, amount1) = (amount0Optimal, amount1Desired);
}
uint256 totalSupply = ITwapPair(pair).totalSupply();
uint256 liquidityOut = Math.min(amount0.mul(totalSupply) / reserve0, amount1.mul(totalSupply) / reserve1);
if (liquidityOut == 0) {
amount0 = 0;
amount1 = 0;
}
}
}
function addLiquidityAndMint(
address pair,
address to,
address token0,
address token1,
uint256 amount0Desired,
uint256 amount1Desired
) external returns (uint256 amount0Left, uint256 amount1Left, uint256 swapToken) {
uint256 amount0;
uint256 amount1;
(amount0, amount1, swapToken) = addLiquidity(pair, amount0Desired, amount1Desired);
if (amount0 == 0 || amount1 == 0) {
return (amount0Desired, amount1Desired, swapToken);
}
TransferHelper.safeTransfer(token0, pair, amount0);
TransferHelper.safeTransfer(token1, pair, amount1);
ITwapPair(pair).mint(to);
amount0Left = amount0Desired.sub(amount0);
amount1Left = amount1Desired.sub(amount1);
}
function swapDeposit0(
address pair,
address token0,
uint256 amount0,
uint256 minSwapPrice,
uint16 tolerance,
bytes calldata data
) external returns (uint256 amount0Left, uint256 amount1Left) {
uint256 amount0In = ITwapPair(pair).getDepositAmount0In(amount0, data);
amount1Left = ITwapPair(pair).getSwapAmount1Out(amount0In, data).sub(tolerance);
if (amount1Left == 0) {
return (amount0, amount1Left);
}
uint256 price = getPrice(amount0In, amount1Left, pair);
require(minSwapPrice == 0 || price >= minSwapPrice, 'AL15');
TransferHelper.safeTransfer(token0, pair, amount0In);
ITwapPair(pair).swap(0, amount1Left, address(this), data);
amount0Left = amount0.sub(amount0In);
}
function swapDeposit1(
address pair,
address token1,
uint256 amount1,
uint256 maxSwapPrice,
uint16 tolerance,
bytes calldata data
) external returns (uint256 amount0Left, uint256 amount1Left) {
uint256 amount1In = ITwapPair(pair).getDepositAmount1In(amount1, data);
amount0Left = ITwapPair(pair).getSwapAmount0Out(amount1In, data).sub(tolerance);
if (amount0Left == 0) {
return (amount0Left, amount1);
}
uint256 price = getPrice(amount0Left, amount1In, pair);
require(maxSwapPrice == 0 || price <= maxSwapPrice, 'AL16');
TransferHelper.safeTransfer(token1, pair, amount1In);
ITwapPair(pair).swap(amount0Left, 0, address(this), data);
amount1Left = amount1.sub(amount1In);
}
function getPrice(uint256 amount0, uint256 amount1, address pair) internal view returns (uint256) {
ITwapOracle oracle = ITwapOracle(ITwapPair(pair).oracle());
return amount1.mul(uint256(oracle.decimalsConverter())).div(amount0);
}
function _refundDeposit(address to, address token0, address token1, uint256 amount0, uint256 amount1) internal {
if (amount0 > 0) {
TransferHelper.safeTransfer(token0, to, amount0);
}
if (amount1 > 0) {
TransferHelper.safeTransfer(token1, to, amount1);
}
}
}
ExecutionHelper.sol 304 lines
pragma solidity 0.7.6;
pragma abicoder v2;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
import '../interfaces/ITwapOracle.sol';
import '../interfaces/ITwapPair.sol';
import '../interfaces/IWETH.sol';
import '../libraries/SafeMath.sol';
import '../libraries/Orders.sol';
import '../libraries/TokenShares.sol';
import '../libraries/AddLiquidity.sol';
import '../libraries/WithdrawHelper.sol';
library ExecutionHelper {
using SafeMath for uint256;
using TransferHelper for address;
using Orders for Orders.Data;
using TokenShares for TokenShares.Data;
uint256 private constant ORDER_LIFESPAN = 48 hours;
struct ExecuteBuySellParams {
Orders.Order order;
address pairAddress;
uint16 pairTolerance;
}
function executeDeposit(
Orders.Order calldata order,
address pairAddress,
uint16 pairTolerance,
TokenShares.Data storage tokenShares
) external {
require(order.validAfterTimestamp + ORDER_LIFESPAN >= block.timestamp, 'EH04');
(uint256 amount0Left, uint256 amount1Left, uint256 swapToken) = _initialDeposit(
order,
pairAddress,
tokenShares
);
if (order.swap && swapToken != 0) {
bytes memory data = encodePriceInfo(pairAddress, order.priceAccumulator, order.timestamp);
if (amount0Left != 0 && swapToken == 1) {
uint256 extraAmount1;
(amount0Left, extraAmount1) = AddLiquidity.swapDeposit0(
pairAddress,
order.token0,
amount0Left,
order.minSwapPrice,
pairTolerance,
data
);
amount1Left = amount1Left.add(extraAmount1);
} else if (amount1Left != 0 && swapToken == 2) {
uint256 extraAmount0;
(extraAmount0, amount1Left) = AddLiquidity.swapDeposit1(
pairAddress,
order.token1,
amount1Left,
order.maxSwapPrice,
pairTolerance,
data
);
amount0Left = amount0Left.add(extraAmount0);
}
}
if (amount0Left != 0 && amount1Left != 0) {
(amount0Left, amount1Left, ) = AddLiquidity.addLiquidityAndMint(
pairAddress,
order.to,
order.token0,
order.token1,
amount0Left,
amount1Left
);
}
AddLiquidity._refundDeposit(order.to, order.token0, order.token1, amount0Left, amount1Left);
}
function _initialDeposit(
Orders.Order calldata order,
address pairAddress,
TokenShares.Data storage tokenShares
) private returns (uint256 amount0Left, uint256 amount1Left, uint256 swapToken) {
uint256 amount0Desired = tokenShares.sharesToAmount(order.token0, order.value0, order.amountLimit0, order.to);
uint256 amount1Desired = tokenShares.sharesToAmount(order.token1, order.value1, order.amountLimit1, order.to);
(amount0Left, amount1Left, swapToken) = AddLiquidity.addLiquidityAndMint(
pairAddress,
order.to,
order.token0,
order.token1,
amount0Desired,
amount1Desired
);
}
function executeWithdraw(Orders.Order calldata order) external {
require(order.validAfterTimestamp + ORDER_LIFESPAN >= block.timestamp, 'EH04');
(address pairAddress, ) = Orders.getPair(order.token0, order.token1);
TransferHelper.safeTransfer(pairAddress, pairAddress, order.liquidity);
uint256 wethAmount;
uint256 amount0;
uint256 amount1;
if (order.unwrap && (order.token0 == TokenShares.WETH_ADDRESS || order.token1 == TokenShares.WETH_ADDRESS)) {
bool success;
(success, wethAmount, amount0, amount1) = WithdrawHelper.withdrawAndUnwrap(
order.token0,
order.token1,
pairAddress,
TokenShares.WETH_ADDRESS,
order.to,
Orders.getTransferGasCost(Orders.NATIVE_CURRENCY_SENTINEL)
);
if (!success) {
TokenShares.onUnwrapFailed(order.to, wethAmount);
}
} else {
(amount0, amount1) = ITwapPair(pairAddress).burn(order.to);
}
require(amount0 >= order.value0 && amount1 >= order.value1, 'EH03');
}
function executeBuy(ExecuteBuySellParams memory orderParams, TokenShares.Data storage tokenShares) external {
require(orderParams.order.validAfterTimestamp + ORDER_LIFESPAN >= block.timestamp, 'EH04');
uint256 amountInMax = tokenShares.sharesToAmount(
orderParams.order.token0,
orderParams.order.value0,
orderParams.order.amountLimit0,
orderParams.order.to
);
bytes memory priceInfo = encodePriceInfo(
orderParams.pairAddress,
orderParams.order.priceAccumulator,
orderParams.order.timestamp
);
uint256 amountIn;
uint256 amountOut;
uint256 reserveOut;
bool inverted = orderParams.order.inverted;
{
// scope for reserve out logic, avoids stack too deep errors
(uint112 reserve0, uint112 reserve1) = ITwapPair(orderParams.pairAddress).getReserves();
// subtract 1 to prevent reserve going to 0
reserveOut = uint256(inverted ? reserve0 : reserve1).sub(1);
}
{
// scope for partial fill logic, avoids stack too deep errors
address oracle = ITwapPair(orderParams.pairAddress).oracle();
uint256 swapFee = ITwapPair(orderParams.pairAddress).swapFee();
(amountIn, amountOut) = ITwapOracle(oracle).getSwapAmountInMaxOut(
inverted,
swapFee,
orderParams.order.value1,
priceInfo
);
uint256 amountInMaxScaled;
if (amountOut > reserveOut) {
amountInMaxScaled = amountInMax.mul(reserveOut).ceil_div(orderParams.order.value1);
(amountIn, amountOut) = ITwapOracle(oracle).getSwapAmountInMinOut(
inverted,
swapFee,
reserveOut,
priceInfo
);
} else {
amountInMaxScaled = amountInMax;
amountOut = orderParams.order.value1; // Truncate to desired out
}
require(amountInMaxScaled >= amountIn, 'EH08');
if (amountInMax > amountIn) {
if (orderParams.order.token0 == TokenShares.WETH_ADDRESS && orderParams.order.unwrap) {
forceEtherTransfer(orderParams.order.to, amountInMax.sub(amountIn));
} else {
TransferHelper.safeTransfer(
orderParams.order.token0,
orderParams.order.to,
amountInMax.sub(amountIn)
);
}
}
TransferHelper.safeTransfer(orderParams.order.token0, orderParams.pairAddress, amountIn);
}
amountOut = amountOut.sub(orderParams.pairTolerance);
uint256 amount0Out;
uint256 amount1Out;
if (inverted) {
amount0Out = amountOut;
} else {
amount1Out = amountOut;
}
if (orderParams.order.token1 == TokenShares.WETH_ADDRESS && orderParams.order.unwrap) {
ITwapPair(orderParams.pairAddress).swap(amount0Out, amount1Out, address(this), priceInfo);
forceEtherTransfer(orderParams.order.to, amountOut);
} else {
ITwapPair(orderParams.pairAddress).swap(amount0Out, amount1Out, orderParams.order.to, priceInfo);
}
}
function executeSell(ExecuteBuySellParams memory orderParams, TokenShares.Data storage tokenShares) external {
require(orderParams.order.validAfterTimestamp + ORDER_LIFESPAN >= block.timestamp, 'EH04');
bytes memory priceInfo = encodePriceInfo(
orderParams.pairAddress,
orderParams.order.priceAccumulator,
orderParams.order.timestamp
);
uint256 amountOut = _executeSellHelper(orderParams, priceInfo, tokenShares);
(uint256 amount0Out, uint256 amount1Out) = orderParams.order.inverted
? (amountOut, uint256(0))
: (uint256(0), amountOut);
if (orderParams.order.token1 == TokenShares.WETH_ADDRESS && orderParams.order.unwrap) {
ITwapPair(orderParams.pairAddress).swap(amount0Out, amount1Out, address(this), priceInfo);
forceEtherTransfer(orderParams.order.to, amountOut);
} else {
ITwapPair(orderParams.pairAddress).swap(amount0Out, amount1Out, orderParams.order.to, priceInfo);
}
}
function _executeSellHelper(
ExecuteBuySellParams memory orderParams,
bytes memory priceInfo,
TokenShares.Data storage tokenShares
) internal returns (uint256 amountOut) {
uint256 reserveOut;
{
// scope for determining reserve out, avoids stack too deep errors
(uint112 reserve0, uint112 reserve1) = ITwapPair(orderParams.pairAddress).getReserves();
// subtract 1 to prevent reserve going to 0
reserveOut = uint256(orderParams.order.inverted ? reserve0 : reserve1).sub(1);
}
{
// scope for calculations, avoids stack too deep errors
address oracle = ITwapPair(orderParams.pairAddress).oracle();
uint256 swapFee = ITwapPair(orderParams.pairAddress).swapFee();
uint256 amountIn = tokenShares.sharesToAmount(
orderParams.order.token0,
orderParams.order.value0,
orderParams.order.amountLimit0,
orderParams.order.to
);
amountOut = orderParams.order.inverted
? ITwapOracle(oracle).getSwapAmount0Out(swapFee, amountIn, priceInfo)
: ITwapOracle(oracle).getSwapAmount1Out(swapFee, amountIn, priceInfo);
uint256 amountOutMinScaled;
if (amountOut > reserveOut) {
amountOutMinScaled = orderParams.order.value1.mul(reserveOut).div(amountOut);
uint256 _amountIn = amountIn;
(amountIn, amountOut) = ITwapOracle(oracle).getSwapAmountInMinOut(
orderParams.order.inverted,
swapFee,
reserveOut,
priceInfo
);
if (orderParams.order.token0 == TokenShares.WETH_ADDRESS && orderParams.order.unwrap) {
forceEtherTransfer(orderParams.order.to, _amountIn.sub(amountIn));
} else {
TransferHelper.safeTransfer(
orderParams.order.token0,
orderParams.order.to,
_amountIn.sub(amountIn)
);
}
} else {
amountOutMinScaled = orderParams.order.value1;
}
amountOut = amountOut.sub(orderParams.pairTolerance);
require(amountOut >= amountOutMinScaled, 'EH37');
TransferHelper.safeTransfer(orderParams.order.token0, orderParams.pairAddress, amountIn);
}
}
function encodePriceInfo(
address pairAddress,
uint256 priceAccumulator,
uint256 priceTimestamp
) internal view returns (bytes memory data) {
uint256 price = ITwapOracle(ITwapPair(pairAddress).oracle()).getAveragePrice(priceAccumulator, priceTimestamp);
// Pack everything as 32 bytes / uint256 to simplify decoding
data = abi.encode(price);
}
function forceEtherTransfer(address to, uint256 amount) internal {
IWETH(TokenShares.WETH_ADDRESS).withdraw(amount);
(bool success, ) = to.call{ value: amount, gas: Orders.getTransferGasCost(Orders.NATIVE_CURRENCY_SENTINEL) }(
''
);
if (!success) {
TokenShares.onUnwrapFailed(to, amount);
}
}
}
Math.sol 32 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
// a library for performing various math operations
library Math {
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = x < y ? x : y;
}
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = x > y ? x : y;
}
// babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}
Orders.sol 647 lines
pragma solidity 0.7.6;
pragma abicoder v2;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
import './SafeMath.sol';
import '../libraries/Math.sol';
import '../interfaces/ITwapFactory.sol';
import '../interfaces/ITwapPair.sol';
import '../interfaces/ITwapOracle.sol';
import '../libraries/TokenShares.sol';
library Orders {
using SafeMath for uint256;
using TokenShares for TokenShares.Data;
using TransferHelper for address;
enum OrderType {
Empty,
Deposit,
Withdraw,
Sell,
Buy
}
enum OrderStatus {
NonExistent,
EnqueuedWaiting,
EnqueuedReady,
ExecutedSucceeded,
ExecutedFailed,
Canceled
}
event DepositEnqueued(uint256 indexed orderId, Order order);
event WithdrawEnqueued(uint256 indexed orderId, Order order);
event SellEnqueued(uint256 indexed orderId, Order order);
event BuyEnqueued(uint256 indexed orderId, Order order);
event OrderTypesDisabled(address pair, Orders.OrderType[] orderTypes, bool disabled);
event RefundFailed(address indexed to, address indexed token, uint256 amount, bytes data);
// Note on gas estimation for the full order execution in the UI:
// Add (*_ORDER_BASE_COST + token transfer costs) to the actual gas usage
// of the TwapDelay._execute* functions when updating gas cost in the UI.
// Remember that ETH unwrap is part of those functions. It is optional,
// but also needs to be included in the estimate.
uint256 public constant ETHER_TRANSFER_COST = ETHER_TRANSFER_CALL_COST + 2600 + 1504; // Std cost + EIP-2929 acct access cost + Gnosis Safe receive ETH cost
uint256 private constant BOT_ETHER_TRANSFER_COST = 10_000;
uint256 private constant BUFFER_COST = 10_000;
uint256 private constant ORDER_EXECUTED_EVENT_COST = 3700;
uint256 private constant EXECUTE_PREPARATION_COST = 30_000; // dequeue + gas calculation before calls to _execute* functions
uint256 public constant ETHER_TRANSFER_CALL_COST = 10_000;
uint256 public constant PAIR_TRANSFER_COST = 55_000;
uint256 public constant REFUND_BASE_COST =
BOT_ETHER_TRANSFER_COST + ETHER_TRANSFER_COST + BUFFER_COST + ORDER_EXECUTED_EVENT_COST;
uint256 private constant ORDER_BASE_COST = EXECUTE_PREPARATION_COST + REFUND_BASE_COST;
uint256 public constant TOKEN_REFUND_BASE_COST = 20_000; // cost of performing token refund logic (excluding token transfer)
uint256 public constant DEPOSIT_ORDER_BASE_COST = ORDER_BASE_COST + 2 * TOKEN_REFUND_BASE_COST;
uint256 public constant WITHDRAW_ORDER_BASE_COST = ORDER_BASE_COST;
uint256 public constant SELL_ORDER_BASE_COST = ORDER_BASE_COST + TOKEN_REFUND_BASE_COST;
uint256 public constant BUY_ORDER_BASE_COST = ORDER_BASE_COST + TOKEN_REFUND_BASE_COST;
// Masks used for setting order disabled
// Different bits represent different order types
uint8 private constant DEPOSIT_MASK = uint8(1 << uint8(OrderType.Deposit)); // 00000010
uint8 private constant WITHDRAW_MASK = uint8(1 << uint8(OrderType.Withdraw)); // 00000100
uint8 private constant SELL_MASK = uint8(1 << uint8(OrderType.Sell)); // 00001000
uint8 private constant BUY_MASK = uint8(1 << uint8(OrderType.Buy)); // 00010000
address public constant FACTORY_ADDRESS = 0xC480b33eE5229DE3FbDFAD1D2DCD3F3BAD0C56c6;
uint256 public constant MAX_GAS_LIMIT = 5000000;
uint256 public constant GAS_PRICE_INERTIA = 20000000;
uint256 public constant MAX_GAS_PRICE_IMPACT = 1000000;
uint256 public constant DELAY = 1800;
address public constant NATIVE_CURRENCY_SENTINEL = address(0); // A sentinel value for the native currency to distinguish it from ERC20 tokens
struct Data {
uint256 newestOrderId;
uint256 lastProcessedOrderId;
mapping(uint256 => bytes32) orderQueue;
uint256 gasPrice;
mapping(uint256 => bool) canceled;
// Bit on specific positions indicates whether order type is disabled (1) or enabled (0) on specific pair
mapping(address => uint8) orderTypesDisabled;
mapping(uint256 => bool) refundFailed;
}
struct Order {
uint256 orderId;
OrderType orderType;
bool inverted;
uint256 validAfterTimestamp;
bool unwrap;
uint256 timestamp;
uint256 gasLimit;
uint256 gasPrice;
uint256 liquidity;
uint256 value0; // Deposit: share0, Withdraw: amount0Min, Sell: shareIn, Buy: shareInMax
uint256 value1; // Deposit: share1, Withdraw: amount1Min, Sell: amountOutMin, Buy: amountOut
address token0; // Sell: tokenIn, Buy: tokenIn
address token1; // Sell: tokenOut, Buy: tokenOut
address to;
uint256 minSwapPrice;
uint256 maxSwapPrice;
bool swap;
uint256 priceAccumulator;
uint256 amountLimit0;
uint256 amountLimit1;
}
function getOrderStatus(
Data storage data,
uint256 orderId,
uint256 validAfterTimestamp
) internal view returns (OrderStatus) {
if (orderId > data.newestOrderId) {
return OrderStatus.NonExistent;
}
if (data.canceled[orderId]) {
return OrderStatus.Canceled;
}
if (data.refundFailed[orderId]) {
return OrderStatus.ExecutedFailed;
}
if (data.orderQueue[orderId] == bytes32(0)) {
return OrderStatus.ExecutedSucceeded;
}
if (validAfterTimestamp >= block.timestamp) {
return OrderStatus.EnqueuedWaiting;
}
return OrderStatus.EnqueuedReady;
}
function getPair(address tokenA, address tokenB) internal view returns (address pair, bool inverted) {
pair = ITwapFactory(FACTORY_ADDRESS).getPair(tokenA, tokenB);
require(pair != address(0), 'OS17');
inverted = tokenA > tokenB;
}
function getDepositDisabled(Data storage data, address pair) internal view returns (bool) {
return data.orderTypesDisabled[pair] & DEPOSIT_MASK != 0;
}
function getWithdrawDisabled(Data storage data, address pair) internal view returns (bool) {
return data.orderTypesDisabled[pair] & WITHDRAW_MASK != 0;
}
function getSellDisabled(Data storage data, address pair) internal view returns (bool) {
return data.orderTypesDisabled[pair] & SELL_MASK != 0;
}
function getBuyDisabled(Data storage data, address pair) internal view returns (bool) {
return data.orderTypesDisabled[pair] & BUY_MASK != 0;
}
function setOrderTypesDisabled(
Data storage data,
address pair,
Orders.OrderType[] calldata orderTypes,
bool disabled
) external {
uint256 orderTypesLength = orderTypes.length;
uint8 currentSettings = data.orderTypesDisabled[pair];
uint8 combinedMask;
for (uint256 i; i < orderTypesLength; ++i) {
Orders.OrderType orderType = orderTypes[i];
require(orderType != Orders.OrderType.Empty, 'OS32');
// zeros with 1 bit set at position specified by orderType
// e.g. for SELL order type
// mask for SELL = 00001000
// combinedMask = 00000110 (DEPOSIT and WITHDRAW masks set in previous iterations)
// the result of OR = 00001110 (DEPOSIT, WITHDRAW and SELL combined mask)
combinedMask = combinedMask | uint8(1 << uint8(orderType));
}
// set/unset a bit accordingly to 'disabled' value
if (disabled) {
// OR operation to disable order
// e.g. for disable DEPOSIT
// currentSettings = 00010100 (BUY and WITHDRAW disabled)
// mask for DEPOSIT = 00000010
// the result of OR = 00010110
currentSettings = currentSettings | combinedMask;
} else {
// AND operation with a mask negation to enable order
// e.g. for enable DEPOSIT
// currentSettings = 00010100 (BUY and WITHDRAW disabled)
// 0xff = 11111111
// mask for Deposit = 00000010
// mask negation = 11111101
// the result of AND = 00010100
currentSettings = currentSettings & (combinedMask ^ 0xff);
}
require(currentSettings != data.orderTypesDisabled[pair], 'OS01');
data.orderTypesDisabled[pair] = currentSettings;
emit OrderTypesDisabled(pair, orderTypes, disabled);
}
function markRefundFailed(Data storage data) internal {
data.refundFailed[data.lastProcessedOrderId] = true;
}
/// @dev The passed in order.oderId is ignored and overwritten with the correct value, i.e. an updated data.newestOrderId.
/// This is done to ensure atomicity of these two actions while optimizing gas usage - adding an order to the queue and incrementing
/// data.newestOrderId (which should not be done anywhere else in the contract).
/// Must only be called on verified orders.
function enqueueOrder(Data storage data, Order memory order) internal {
order.orderId = ++data.newestOrderId;
data.orderQueue[order.orderId] = getOrderDigest(order);
}
struct DepositParams {
address token0;
address token1;
uint256 amount0;
uint256 amount1;
uint256 minSwapPrice;
uint256 maxSwapPrice;
bool wrap;
bool swap;
address to;
uint256 gasLimit;
uint32 submitDeadline;
}
function deposit(
Data storage data,
DepositParams calldata depositParams,
TokenShares.Data storage tokenShares
) external {
checkOrderParams(
depositParams.to,
depositParams.gasLimit,
depositParams.submitDeadline,
DEPOSIT_ORDER_BASE_COST +
getTransferGasCost(depositParams.token0) +
getTransferGasCost(depositParams.token1)
);
require(depositParams.amount0 != 0 || depositParams.amount1 != 0, 'OS25');
(address pairAddress, bool inverted) = getPair(depositParams.token0, depositParams.token1);
require(!getDepositDisabled(data, pairAddress), 'OS46');
{
// scope for value, avoids stack too deep errors
uint256 value = msg.value;
// allocate gas refund
if (depositParams.wrap) {
if (depositParams.token0 == TokenShares.WETH_ADDRESS) {
value = msg.value.sub(depositParams.amount0, 'OS1E');
} else if (depositParams.token1 == TokenShares.WETH_ADDRESS) {
value = msg.value.sub(depositParams.amount1, 'OS1E');
}
}
allocateGasRefund(data, value, depositParams.gasLimit);
}
uint256 shares0 = tokenShares.amountToShares(
inverted ? depositParams.token1 : depositParams.token0,
inverted ? depositParams.amount1 : depositParams.amount0,
depositParams.wrap
);
uint256 shares1 = tokenShares.amountToShares(
inverted ? depositParams.token0 : depositParams.token1,
inverted ? depositParams.amount0 : depositParams.amount1,
depositParams.wrap
);
(uint256 priceAccumulator, uint256 timestamp) = ITwapOracle(ITwapPair(pairAddress).oracle()).getPriceInfo();
Order memory order = Order(
0,
OrderType.Deposit,
inverted,
timestamp + DELAY, // validAfterTimestamp
depositParams.wrap,
timestamp,
depositParams.gasLimit,
data.gasPrice,
0, // liquidity
shares0,
shares1,
inverted ? depositParams.token1 : depositParams.token0,
inverted ? depositParams.token0 : depositParams.token1,
depositParams.to,
depositParams.minSwapPrice,
depositParams.maxSwapPrice,
depositParams.swap,
priceAccumulator,
inverted ? depositParams.amount1 : depositParams.amount0,
inverted ? depositParams.amount0 : depositParams.amount1
);
enqueueOrder(data, order);
emit DepositEnqueued(order.orderId, order);
}
struct WithdrawParams {
address token0;
address token1;
uint256 liquidity;
uint256 amount0Min;
uint256 amount1Min;
bool unwrap;
address to;
uint256 gasLimit;
uint32 submitDeadline;
}
function withdraw(Data storage data, WithdrawParams calldata withdrawParams) external {
(address pair, bool inverted) = getPair(withdrawParams.token0, withdrawParams.token1);
require(!getWithdrawDisabled(data, pair), 'OS0A');
checkOrderParams(
withdrawParams.to,
withdrawParams.gasLimit,
withdrawParams.submitDeadline,
WITHDRAW_ORDER_BASE_COST + PAIR_TRANSFER_COST
);
require(withdrawParams.liquidity != 0, 'OS22');
allocateGasRefund(data, msg.value, withdrawParams.gasLimit);
pair.safeTransferFrom(msg.sender, address(this), withdrawParams.liquidity);
Order memory order = Order(
0,
OrderType.Withdraw,
inverted,
block.timestamp + DELAY, // validAfterTimestamp
withdrawParams.unwrap,
0, // timestamp
withdrawParams.gasLimit,
data.gasPrice,
withdrawParams.liquidity,
inverted ? withdrawParams.amount1Min : withdrawParams.amount0Min,
inverted ? withdrawParams.amount0Min : withdrawParams.amount1Min,
inverted ? withdrawParams.token1 : withdrawParams.token0,
inverted ? withdrawParams.token0 : withdrawParams.token1,
withdrawParams.to,
0, // minSwapPrice
0, // maxSwapPrice
false, // swap
0, // priceAccumulator
0, // amountLimit0
0 // amountLimit1
);
enqueueOrder(data, order);
emit WithdrawEnqueued(order.orderId, order);
}
struct SellParams {
address tokenIn;
address tokenOut;
uint256 amountIn;
uint256 amountOutMin;
bool wrapUnwrap;
address to;
uint256 gasLimit;
uint32 submitDeadline;
}
function sell(Data storage data, SellParams calldata sellParams, TokenShares.Data storage tokenShares) external {
checkOrderParams(
sellParams.to,
sellParams.gasLimit,
sellParams.submitDeadline,
SELL_ORDER_BASE_COST + getTransferGasCost(sellParams.tokenIn)
);
(address pairAddress, bool inverted) = sellHelper(data, sellParams);
(uint256 priceAccumulator, uint256 timestamp) = ITwapOracle(ITwapPair(pairAddress).oracle()).getPriceInfo();
uint256 shares = tokenShares.amountToShares(sellParams.tokenIn, sellParams.amountIn, sellParams.wrapUnwrap);
Order memory order = Order(
0,
OrderType.Sell,
inverted,
timestamp + DELAY, // validAfterTimestamp
sellParams.wrapUnwrap,
timestamp,
sellParams.gasLimit,
data.gasPrice,
0, // liquidity
shares,
sellParams.amountOutMin,
sellParams.tokenIn,
sellParams.tokenOut,
sellParams.to,
0, // minSwapPrice
0, // maxSwapPrice
false, // swap
priceAccumulator,
sellParams.amountIn,
0 // amountLimit1
);
enqueueOrder(data, order);
emit SellEnqueued(order.orderId, order);
}
function relayerSell(
Data storage data,
SellParams calldata sellParams,
TokenShares.Data storage tokenShares
) external {
checkOrderParams(
sellParams.to,
sellParams.gasLimit,
sellParams.submitDeadline,
SELL_ORDER_BASE_COST + getTransferGasCost(sellParams.tokenIn)
);
(, bool inverted) = sellHelper(data, sellParams);
uint256 shares = tokenShares.amountToSharesWithoutTransfer(
sellParams.tokenIn,
sellParams.amountIn,
sellParams.wrapUnwrap
);
Order memory order = Order(
0,
OrderType.Sell,
inverted,
block.timestamp + DELAY, // validAfterTimestamp
false, // Never wrap/unwrap
block.timestamp,
sellParams.gasLimit,
data.gasPrice,
0, // liquidity
shares,
sellParams.amountOutMin,
sellParams.tokenIn,
sellParams.tokenOut,
sellParams.to,
0, // minSwapPrice
0, // maxSwapPrice
false, // swap
0, // priceAccumulator - oracleV3 pairs don't need priceAccumulator
sellParams.amountIn,
0 // amountLimit1
);
enqueueOrder(data, order);
emit SellEnqueued(order.orderId, order);
}
function sellHelper(
Data storage data,
SellParams calldata sellParams
) internal returns (address pairAddress, bool inverted) {
require(sellParams.amountIn != 0, 'OS24');
(pairAddress, inverted) = getPair(sellParams.tokenIn, sellParams.tokenOut);
require(!getSellDisabled(data, pairAddress), 'OS13');
// allocate gas refund
uint256 value = msg.value;
if (sellParams.wrapUnwrap && sellParams.tokenIn == TokenShares.WETH_ADDRESS) {
value = msg.value.sub(sellParams.amountIn, 'OS1E');
}
allocateGasRefund(data, value, sellParams.gasLimit);
}
struct BuyParams {
address tokenIn;
address tokenOut;
uint256 amountInMax;
uint256 amountOut;
bool wrapUnwrap;
address to;
uint256 gasLimit;
uint32 submitDeadline;
}
function buy(Data storage data, BuyParams calldata buyParams, TokenShares.Data storage tokenShares) external {
checkOrderParams(
buyParams.to,
buyParams.gasLimit,
buyParams.submitDeadline,
BUY_ORDER_BASE_COST + getTransferGasCost(buyParams.tokenIn)
);
require(buyParams.amountOut != 0, 'OS23');
(address pairAddress, bool inverted) = getPair(buyParams.tokenIn, buyParams.tokenOut);
require(!getBuyDisabled(data, pairAddress), 'OS49');
uint256 value = msg.value;
// allocate gas refund
if (buyParams.tokenIn == TokenShares.WETH_ADDRESS && buyParams.wrapUnwrap) {
value = msg.value.sub(buyParams.amountInMax, 'OS1E');
}
allocateGasRefund(data, value, buyParams.gasLimit);
uint256 shares = tokenShares.amountToShares(buyParams.tokenIn, buyParams.amountInMax, buyParams.wrapUnwrap);
(uint256 priceAccumulator, uint256 timestamp) = ITwapOracle(ITwapPair(pairAddress).oracle()).getPriceInfo();
Order memory order = Order(
0,
OrderType.Buy,
inverted,
timestamp + DELAY, // validAfterTimestamp
buyParams.wrapUnwrap,
timestamp,
buyParams.gasLimit,
data.gasPrice,
0, // liquidity
shares,
buyParams.amountOut,
buyParams.tokenIn,
buyParams.tokenOut,
buyParams.to,
0, // minSwapPrice
0, // maxSwapPrice
false, // swap
priceAccumulator,
buyParams.amountInMax,
0 // amountLimit1
);
enqueueOrder(data, order);
emit BuyEnqueued(order.orderId, order);
}
function checkOrderParams(address to, uint256 gasLimit, uint32 submitDeadline, uint256 minGasLimit) private view {
require(submitDeadline >= block.timestamp, 'OS04');
require(gasLimit <= MAX_GAS_LIMIT, 'OS3E');
require(gasLimit >= minGasLimit, 'OS3D');
require(to != address(0), 'OS26');
}
function allocateGasRefund(Data storage data, uint256 value, uint256 gasLimit) private returns (uint256 futureFee) {
futureFee = data.gasPrice.mul(gasLimit);
require(value >= futureFee, 'OS1E');
if (value > futureFee) {
TransferHelper.safeTransferETH(msg.sender, value - futureFee, getTransferGasCost(NATIVE_CURRENCY_SENTINEL));
}
}
function updateGasPrice(Data storage data, uint256 gasUsed) external {
uint256 scale = Math.min(gasUsed, MAX_GAS_PRICE_IMPACT);
data.gasPrice = data.gasPrice.mul(GAS_PRICE_INERTIA.sub(scale)).add(tx.gasprice.mul(scale)).div(
GAS_PRICE_INERTIA
);
}
function refundLiquidity(address pair, address to, uint256 liquidity, bytes4 selector) internal returns (bool) {
if (liquidity == 0) {
return true;
}
(bool success, bytes memory data) = address(this).call{ gas: PAIR_TRANSFER_COST }(
abi.encodeWithSelector(selector, pair, to, liquidity, false)
);
if (!success) {
emit RefundFailed(to, pair, liquidity, data);
}
return success;
}
function dequeueOrder(Data storage data, uint256 orderId) internal {
++data.lastProcessedOrderId;
require(orderId == data.lastProcessedOrderId, 'OS72');
}
function forgetOrder(Data storage data, uint256 orderId) internal {
delete data.orderQueue[orderId];
}
function forgetLastProcessedOrder(Data storage data) internal {
delete data.orderQueue[data.lastProcessedOrderId];
}
function getOrderDigest(Order memory order) internal pure returns (bytes32) {
// Used to avoid the 'stack too deep' error.
bytes memory partialOrderData = abi.encodePacked(
order.orderId,
order.orderType,
order.inverted,
order.validAfterTimestamp,
order.unwrap,
order.timestamp,
order.gasLimit,
order.gasPrice,
order.liquidity,
order.value0,
order.value1,
order.token0,
order.token1,
order.to
);
return
keccak256(
abi.encodePacked(
partialOrderData,
order.minSwapPrice,
order.maxSwapPrice,
order.swap,
order.priceAccumulator,
order.amountLimit0,
order.amountLimit1
)
);
}
function verifyOrder(Data storage data, Order memory order) external view {
require(getOrderDigest(order) == data.orderQueue[order.orderId], 'OS71');
}
// constant mapping for transferGasCost
/**
* @dev This function should either return a default value != 0 or revert.
*/
function getTransferGasCost(address token) internal pure returns (uint256) {
if (token == NATIVE_CURRENCY_SENTINEL) return ETHER_TRANSFER_CALL_COST;
if (token == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) return 31000;
if (token == 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) return 42000;
if (token == 0xdAC17F958D2ee523a2206206994597C13D831ec7) return 66000;
if (token == 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599) return 34000;
if (token == 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) return 31000;
if (token == 0x6B3595068778DD592e39A122f4f5a5cF09C90fE2) return 31000;
if (token == 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84) return 68000;
if (token == 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0) return 31000;
if (token == 0xD33526068D116cE69F19A9ee46F0bd304F21A51f) return 31000;
if (token == 0x48C3399719B582dD63eB5AADf12A40B4C3f52FA2) return 40000;
if (token == 0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32) return 149000;
if (token == 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2) return 34000;
if (token == 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984) return 37000;
if (token == 0x514910771AF9Ca656af840dff83E8264EcF986CA) return 32000;
if (token == 0x3c3a81e81dc49A522A592e7622A7E711c06bf354) return 34000;
return 60000;
}
}
SafeMath.sol 102 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
library SafeMath {
int256 private constant _INT256_MIN = -2 ** 255;
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x, 'SM4E');
}
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = sub(x, y, 'SM12');
}
function sub(uint256 x, uint256 y, string memory message) internal pure returns (uint256 z) {
require((z = x - y) <= x, message);
}
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(y == 0 || (z = x * y) / y == x, 'SM2A');
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, 'SM43');
return a / b;
}
function ceil_div(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = div(a, b);
if (a != mul(b, c)) {
return add(c, 1);
}
}
function toUint32(uint256 n) internal pure returns (uint32) {
require(n <= type(uint32).max, 'SM50');
return uint32(n);
}
function toUint64(uint256 n) internal pure returns (uint64) {
require(n <= type(uint64).max, 'SM54');
return uint64(n);
}
function toUint112(uint256 n) internal pure returns (uint112) {
require(n <= type(uint112).max, 'SM51');
return uint112(n);
}
function toInt256(uint256 unsigned) internal pure returns (int256 signed) {
require(unsigned <= uint256(type(int256).max), 'SM34');
signed = int256(unsigned);
}
// int256
function add(int256 a, int256 b) internal pure returns (int256 c) {
c = a + b;
require((b >= 0 && c >= a) || (b < 0 && c < a), 'SM4D');
}
function sub(int256 a, int256 b) internal pure returns (int256 c) {
c = a - b;
require((b >= 0 && c <= a) || (b < 0 && c > a), 'SM11');
}
function mul(int256 a, int256 b) internal pure returns (int256 c) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
require(!(a == -1 && b == _INT256_MIN), 'SM29');
c = a * b;
require(c / a == b, 'SM29');
}
function div(int256 a, int256 b) internal pure returns (int256) {
require(b != 0, 'SM43');
require(!(b == -1 && a == _INT256_MIN), 'SM42');
return a / b;
}
function neg_floor_div(int256 a, int256 b) internal pure returns (int256 c) {
c = div(a, b);
if ((a < 0 && b > 0) || (a >= 0 && b < 0)) {
if (a != mul(b, c)) {
c = sub(c, 1);
}
}
}
}
TokenShares.sol 158 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
import '../interfaces/IERC20.sol';
import '../interfaces/IWETH.sol';
import './SafeMath.sol';
import './TransferHelper.sol';
library TokenShares {
using SafeMath for uint256;
using TransferHelper for address;
uint256 private constant PRECISION = 10 ** 18;
uint256 private constant TOLERANCE = 10 ** 18 + 10 ** 16;
uint256 private constant TOTAL_SHARES_PRECISION = 10 ** 18;
event UnwrapFailed(address to, uint256 amount);
// represents wrapped native currency (WETH or WMATIC)
address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
struct Data {
mapping(address => uint256) totalShares;
}
function sharesToAmount(
Data storage data,
address token,
uint256 share,
uint256 amountLimit,
address refundTo
) external returns (uint256) {
if (share == 0) {
return 0;
}
if (token == WETH_ADDRESS || isNonRebasing(token)) {
return share;
}
uint256 totalTokenShares = data.totalShares[token];
require(totalTokenShares >= share, 'TS3A');
uint256 balance = IERC20(token).balanceOf(address(this));
uint256 value = balance.mul(share).div(totalTokenShares);
data.totalShares[token] = totalTokenShares.sub(share);
if (amountLimit > 0) {
uint256 amountLimitWithTolerance = amountLimit.mul(TOLERANCE).div(PRECISION);
if (value > amountLimitWithTolerance) {
TransferHelper.safeTransfer(token, refundTo, value.sub(amountLimitWithTolerance));
return amountLimitWithTolerance;
}
}
return value;
}
function amountToShares(Data storage data, address token, uint256 amount, bool wrap) external returns (uint256) {
if (amount == 0) {
return 0;
}
if (token == WETH_ADDRESS) {
if (wrap) {
require(msg.value >= amount, 'TS03');
IWETH(token).deposit{ value: amount }();
} else {
token.safeTransferFrom(msg.sender, address(this), amount);
}
return amount;
} else if (isNonRebasing(token)) {
token.safeTransferFrom(msg.sender, address(this), amount);
return amount;
} else {
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
token.safeTransferFrom(msg.sender, address(this), amount);
uint256 balanceAfter = IERC20(token).balanceOf(address(this));
return amountToSharesHelper(data, token, balanceBefore, balanceAfter);
}
}
function amountToSharesWithoutTransfer(
Data storage data,
address token,
uint256 amount,
bool wrap
) external returns (uint256) {
if (token == WETH_ADDRESS) {
if (wrap) {
// require(msg.value >= amount, 'TS03'); // Duplicate check in TwapRelayer.sell
IWETH(token).deposit{ value: amount }();
}
return amount;
} else if (isNonRebasing(token)) {
return amount;
} else {
uint256 balanceAfter = IERC20(token).balanceOf(address(this));
uint256 balanceBefore = balanceAfter.sub(amount);
return amountToSharesHelper(data, token, balanceBefore, balanceAfter);
}
}
function amountToSharesHelper(
Data storage data,
address token,
uint256 balanceBefore,
uint256 balanceAfter
) internal returns (uint256) {
uint256 totalTokenShares = data.totalShares[token];
require(balanceBefore > 0 || totalTokenShares == 0, 'TS30');
require(balanceAfter > balanceBefore, 'TS2C');
if (balanceBefore > 0) {
if (totalTokenShares == 0) {
totalTokenShares = balanceBefore.mul(TOTAL_SHARES_PRECISION);
}
uint256 newShares = totalTokenShares.mul(balanceAfter).div(balanceBefore);
require(balanceAfter < type(uint256).max.div(newShares), 'TS73'); // to prevent overflow at execution
data.totalShares[token] = newShares;
return newShares - totalTokenShares;
} else {
totalTokenShares = balanceAfter.mul(TOTAL_SHARES_PRECISION);
require(totalTokenShares < type(uint256).max.div(totalTokenShares), 'TS73'); // to prevent overflow at execution
data.totalShares[token] = totalTokenShares;
return totalTokenShares;
}
}
function onUnwrapFailed(address to, uint256 amount) external {
emit UnwrapFailed(to, amount);
IWETH(WETH_ADDRESS).deposit{ value: amount }();
TransferHelper.safeTransfer(WETH_ADDRESS, to, amount);
}
// constant mapping for nonRebasingToken
function isNonRebasing(address token) internal pure returns (bool) {
if (token == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) return true;
if (token == 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) return true;
if (token == 0xdAC17F958D2ee523a2206206994597C13D831ec7) return true;
if (token == 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599) return true;
if (token == 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) return true;
if (token == 0x6B3595068778DD592e39A122f4f5a5cF09C90fE2) return true;
if (token == 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0) return true;
if (token == 0xD33526068D116cE69F19A9ee46F0bd304F21A51f) return true;
if (token == 0x48C3399719B582dD63eB5AADf12A40B4C3f52FA2) return true;
if (token == 0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32) return true;
if (token == 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2) return true;
if (token == 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984) return true;
if (token == 0x514910771AF9Ca656af840dff83E8264EcF986CA) return true;
if (token == 0x3c3a81e81dc49A522A592e7622A7E711c06bf354) return true;
return false;
}
}
TransferHelper.sol 36 lines
pragma solidity 0.7.6;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(address token, address to, uint256 value) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TH4B');
}
function safeTransfer(address token, address to, uint256 value) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TH05');
}
function safeTransferFrom(address token, address from, address to, uint256 value) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TH0E');
}
function safeTransferETH(address to, uint256 value, uint256 gasLimit) internal {
(bool success, ) = to.call{ value: value, gas: gasLimit }('');
require(success, 'TH3F');
}
function transferETH(address to, uint256 value, uint256 gasLimit) internal returns (bool success) {
(success, ) = to.call{ value: value, gas: gasLimit }('');
}
}
WithdrawHelper.sol 48 lines
pragma solidity 0.7.6;
pragma abicoder v2;
// SPDX-License-Identifier: GPL-3.0-or-later
// Deployed with donations via Gitcoin GR9
import '../interfaces/ITwapPair.sol';
import '../interfaces/IWETH.sol';
import './Orders.sol';
library WithdrawHelper {
using SafeMath for uint256;
function _transferToken(uint256 balanceBefore, address token, address to) internal {
uint256 tokenAmount = IERC20(token).balanceOf(address(this)).sub(balanceBefore);
TransferHelper.safeTransfer(token, to, tokenAmount);
}
// unwraps wrapped native currency
function _unwrapWeth(uint256 ethAmount, address weth, address to, uint256 gasLimit) internal returns (bool) {
IWETH(weth).withdraw(ethAmount);
(bool success, ) = to.call{ value: ethAmount, gas: gasLimit }('');
return success;
}
function withdrawAndUnwrap(
address token0,
address token1,
address pair,
address weth,
address to,
uint256 gasLimit
) external returns (bool, uint256, uint256, uint256) {
bool isToken0Weth = token0 == weth;
address otherToken = isToken0Weth ? token1 : token0;
uint256 balanceBefore = IERC20(otherToken).balanceOf(address(this));
(uint256 amount0, uint256 amount1) = ITwapPair(pair).burn(address(this));
_transferToken(balanceBefore, otherToken, to);
bool success = _unwrapWeth(isToken0Weth ? amount0 : amount1, weth, to, gasLimit);
return (success, isToken0Weth ? amount0 : amount1, amount0, amount1);
}
}
Recent Transactions
This address has 1 on-chain transactions, but only 0.7% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →