Address Contract Partially Verified
Address
0x9f004Dae0487f9F82a5Fa2a1500d53b486e42350
Balance
0 ETH
Nonce
1
Code Size
9268 bytes
Creator
0x499454aE...24d7 at tx 0x2d8e9152...7d4436
Indexed Transactions
0 (1 on-chain, 1.4% indexed)
Contract Bytecode
9268 bytes
0x608060405260043610610164575f3560e01c806366e21b47116100cd5780639fee4c4e11610087578063c34c08e511610062578063c34c08e51461047c578063d4fff981146104af578063e9cbafb0146104c3578063f04f2707146104e2575f5ffd5b80639fee4c4e14610435578063a5871a5f14610454578063a84f29dd14610468575f5ffd5b806366e21b471461037b57806383236ac21461038f57806386a06ff0146103a35780638ec4512d146103d657806391dd7346146103ea578063920f5c8414610416575f5ffd5b806326e56de81161011e57806326e56de8146102a857806330fd3da6146102c757806338af3eed146102ed57806347ca5a42146103205780634f716ec9146103535780635235aa9914610367575f5ffd5b80630acefd351461016f57806310c0a157146101bf57806310d1e85c146101f2578063186f0354146102135780631b11d0ff14610246578063202a536614610275575f5ffd5b3661016b57005b5f5ffd5b34801561017a575f5ffd5b506101a27f000000000000000000000000ae563e3f8219521950555f5962419c8919758ea281565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101ca575f5ffd5b506101a27f000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c881565b3480156101fd575f5ffd5b5061021161020c366004611af0565b610501565b005b34801561021e575f5ffd5b506101a27f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f1781565b348015610251575f5ffd5b50610265610260366004611b54565b61062f565b60405190151581526020016101b6565b348015610280575f5ffd5b506101a27f0000000000000000000000007d2768de32b0b80b7a3454c06bdac94a69ddc7a981565b3480156102b3575f5ffd5b506102656102c2366004611bca565b6107a3565b3480156102d2575f5ffd5b506102db600281565b60405160ff90911681526020016101b6565b3480156102f8575f5ffd5b506101a27f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f1781565b34801561032b575f5ffd5b506101a27f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9081565b34801561035e575f5ffd5b506102db600681565b348015610372575f5ffd5b506102db600981565b348015610386575f5ffd5b506102db600781565b34801561039a575f5ffd5b506102db600881565b3480156103ae575f5ffd5b506101a27f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e281565b3480156103e1575f5ffd5b506102db600381565b3480156103f5575f5ffd5b50610409610404366004611c7c565b610983565b6040516101b69190611cba565b348015610421575f5ffd5b50610265610430366004611d2f565b610a71565b348015610440575f5ffd5b5061026561044f366004611bca565b610c7a565b34801561045f575f5ffd5b506102db600481565b348015610473575f5ffd5b506102db600181565b348015610487575f5ffd5b506101a27f000000000000000000000000499454ae5a1cac12d47d182685895cfb190324d781565b3480156104ba575f5ffd5b506102db600581565b3480156104ce575f5ffd5b506102116104dd366004611e0d565b610e46565b3480156104ed575f5ffd5b506102116104fc366004611e5b565b610eb8565b61050b8282610ff7565b5f546001600160a01b031633146105355760405163379aa0fb60e11b815260040160405180910390fd5b60025460ff16600614801590610551575060025460ff16600914155b1561056f57604051630d3e290960e31b815260040160405180910390fd5b7f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316856001600160a01b0316141580156105ba57506001600160a01b0385163014155b80156105f857507f000000000000000000000000499454ae5a1cac12d47d182685895cfb190324d76001600160a01b0316856001600160a01b031614155b156106165760405163379aa0fb60e11b815260040160405180910390fd5b61062082826111d3565b610628611443565b5050505050565b5f61063a8383610ff7565b336001600160a01b037f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e216146106835760405163379aa0fb60e11b815260040160405180910390fd5b60025460ff166003146106a957604051630d3e290960e31b815260040160405180910390fd5b7f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316846001600160a01b0316141580156106f457506001600160a01b0384163014155b801561073257507f000000000000000000000000499454ae5a1cac12d47d182685895cfb190324d76001600160a01b0316846001600160a01b031614155b1561075057604051630377b97960e11b815260040160405180910390fd5b61075a83836111d3565b61078e877f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2610789888a611f05565b6114ae565b610796611443565b5060019695505050505050565b5f336001600160a01b037f000000000000000000000000499454ae5a1cac12d47d182685895cfb190324d716146107ec576040516282b42960e81b815260040160405180910390fd5b60026003540361080f57604051637863668360e01b815260040160405180910390fd5b60026003556001600160a01b038716158061083257506001600160a01b03871630145b8061086e57507f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316876001600160a01b0316145b15610893576040516394f8539560e01b81525f60048201526024015b60405180910390fd5b61089f898985856115a3565b5f5f886001600160a01b03168888886040516108bc929190611f24565b5f6040518083038185875af1925050503d805f81146108f6576040519150601f19603f3d011682016040523d82523d5f602084013e6108fb565b606091505b509150915081610939576040805160048152602481019091526020810180516001600160e01b031663df3c272f60e01b179052610939908290611680565b600254610100900460ff16156109625760405163711d84bf60e11b815260040160405180910390fd5b61096c8585611697565b600192505050600160035598975050505050505050565b606061098f8383610ff7565b336001600160a01b037f000000000000000000000000ae563e3f8219521950555f5962419c8919758ea216148015906109f15750336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a901614155b15610a0f5760405163379aa0fb60e11b815260040160405180910390fd5b60025460ff16600514801590610a2b575060025460ff16600814155b15610a4957604051630d3e290960e31b815260040160405180910390fd5b610a5383836111d3565b610a5b611443565b5060408051602081019091525f81525b92915050565b5f610a7c8383610ff7565b336001600160a01b037f0000000000000000000000007d2768de32b0b80b7a3454c06bdac94a69ddc7a91614801590610ade5750336001600160a01b037f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e21614155b15610afc5760405163379aa0fb60e11b815260040160405180910390fd5b60025460ff16600114801590610b1757506002805460ff1614155b15610b3557604051630d3e290960e31b815260040160405180910390fd5b7f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316846001600160a01b031614158015610b8057506001600160a01b0384163014155b8015610bbe57507f000000000000000000000000499454ae5a1cac12d47d182685895cfb190324d76001600160a01b0316846001600160a01b031614155b15610bdc57604051630377b97960e11b815260040160405180910390fd5b610be683836111d3565b335f5b8a811015610c6057610c588c8c83818110610c0657610c06611f33565b9050602002016020810190610c1b9190611f47565b838a8a85818110610c2e57610c2e611f33565b905060200201358d8d86818110610c4757610c47611f33565b905060200201356107899190611f05565b600101610be9565b50610c69611443565b5060019a9950505050505050505050565b5f336001600160a01b037f000000000000000000000000499454ae5a1cac12d47d182685895cfb190324d71614610cc3576040516282b42960e81b815260040160405180910390fd5b600260035403610ce657604051637863668360e01b815260040160405180910390fd5b60026003556001600160a01b0387161580610d0957506001600160a01b03871630145b80610d4557507f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316876001600160a01b0316145b15610d65576040516394f8539560e01b81525f600482015260240161088a565b610d71898985856115a3565b5f5f7f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316635229073f8a8a8a8a5f6040518663ffffffff1660e01b8152600401610dc7959493929190611f69565b5f604051808303815f875af1158015610de2573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052610e099190810190612070565b9150915081610939576040805160048152602481019091526020810180516001600160e01b0316633ba4317960e21b179052610939908290611680565b610e508282610ff7565b5f546001600160a01b03163314610e7a5760405163379aa0fb60e11b815260040160405180910390fd5b60025460ff16600714610ea057604051630d3e290960e31b815260040160405180910390fd5b610eaa82826111d3565b610eb2611443565b50505050565b610ec28282610ff7565b336001600160a01b037f000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c81614610f0b5760405163379aa0fb60e11b815260040160405180910390fd5b60025460ff16600414610f3157604051630d3e290960e31b815260040160405180910390fd5b610f3b82826111d3565b5f5b87811015610fe457610fdc7f000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c8868684818110610f7b57610f7b611f33565b90506020020135898985818110610f9457610f94611f33565b90506020020135610fa59190611f05565b8b8b85818110610fb757610fb7611f33565b9050602002016020810190610fcc9190611f47565b6001600160a01b031691906118f6565b600101610f3d565b50610fed611443565b5050505050505050565b600254610100900460ff1661101f576040516346d93a8560e11b815260040160405180910390fd5b6001600160a01b037f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f1716330361106857604051633ca7ceef60e11b815260040160405180910390fd5b5f336001600160a01b037f0000000000000000000000007d2768de32b0b80b7a3454c06bdac94a69ddc7a91614806110c85750336001600160a01b037f00000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e216145b806110fb5750336001600160a01b037f000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c816145b8061112e5750336001600160a01b037f000000000000000000000000ae563e3f8219521950555f5962419c8919758ea216145b806111615750336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9016145b8061117557505f546001600160a01b031633145b9050806111955760405163379aa0fb60e11b815260040160405180910390fd5b60015483836040516111a8929190611f24565b6040518091039020146111ce57604051631d6e22b960e01b815260040160405180910390fd5b505050565b60025462010000900460ff16156111fd5760405163ba02f2b160e01b815260040160405180910390fd5b6002805462ff00001916620100001790555f61121b82840184612231565b505080519091505f5b81811015611430575f83828151811061123f5761123f611f33565b602002602001015190505f6001600160a01b0316815f01516001600160a01b03161480611275575080516001600160a01b031630145b806112b457507f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316815f01516001600160a01b0316145b156112d5576040516394f8539560e01b81526004810183905260240161088a565b6060810151515f5b8181101561135a57611352836060015182815181106112fe576112fe611f33565b60200260200101515f01518460600151838151811061131f5761131f611f33565b6020026020010151602001518560600151848151811061134157611341611f33565b6020026020010151604001516114ae565b6001016112dd565b505f5f835f01516001600160a01b03168460200151856040015160405161138191906123d1565b5f6040518083038185875af1925050503d805f81146113bb576040519150601f19603f3d011682016040523d82523d5f602084013e6113c0565b606091505b509150915081611420576114208163d7d329ab60e01b876040516024016113e991815260200190565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611680565b5050600190920191506112249050565b50506002805462ff000019169055505050565b6002805461ff0019811690915560015460405160ff909216916001600160a01b037f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f1716907ff130245fb78204bd95d4199278b3aee3f8e711d940061e076e537fda113703b7905f90a4565b6001600160a01b0383166114c157505050565b6001600160a01b0382166114e85760405163eff9b16560e01b815260040160405180910390fd5b306001600160a01b038316036115115760405163eff9b16560e01b815260040160405180910390fd5b604051636eb1769f60e11b81523060048201526001600160a01b0383811660248301528491839183169063dd62ed3e90604401602060405180830381865afa15801561155f573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061158391906123e7565b1061158e5750505050565b610eb26001600160a01b038216845f19611955565b60ff841615806115b65750600960ff8516115b156115d457604051630d3e290960e31b815260040160405180910390fd5b7f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316836001600160a01b03160361162657604051633f308fd360e01b815260040160405180910390fd5b6002805460ff191660ff86161790555f80546001600160a01b0319166001600160a01b03851617905560405161165f9083908390611f24565b60405190819003902060015550506002805461ff0019166101001790555050565b81511561168f57815160208301fd5b805160208201fd5b5f806116a583850185612231565b9093509150508015611719576040515f90419083908381818185875af1925050503d805f81146116f0576040519150601f19603f3d011682016040523d82523d5f602084013e6116f5565b606091505b505090508061171757604051631bd529cf60e21b815260040160405180910390fd5b505b81515f5b81811015611854575f84828151811061173857611738611f33565b602002602001015190505f6001600160a01b0316816001600160a01b031603611761575061184c565b6040516370a0823160e01b81523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa1580156117a5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117c991906123e7565b90508015611849576118056001600160a01b0383167f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f17836118f6565b816001600160a01b03167fab2246061d7b0dd3631d037e3f6da75782ae489eeb9f6af878a4b25df9b07c778260405161184091815260200190565b60405180910390a25b50505b60010161171d565b504780156118ee575f7f000000000000000000000000c6139506fa54c450948d9d2d8ccf269453a54f176001600160a01b0316826040515f6040518083038185875af1925050503d805f81146118c5576040519150601f19603f3d011682016040523d82523d5f602084013e6118ca565b606091505b50509050806118ec5760405163d04e9eaf60e01b815260040160405180910390fd5b505b505050505050565b6040516001600160a01b038381166024830152604482018390526111ce91859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506119e0565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b1790526119a68482611a4c565b610eb2576040516001600160a01b0384811660248301525f60448301526119da91869182169063095ea7b390606401611923565b610eb284825b5f5f60205f8451602086015f885af1806119ff576040513d5f823e3d81fd5b50505f513d91508115611a16578060011415611a23565b6001600160a01b0384163b155b15610eb257604051635274afe760e01b81526001600160a01b038516600482015260240161088a565b5f5f5f5f60205f8651602088015f8a5af192503d91505f519050828015611a8b57508115611a7d5780600114611a8b565b5f866001600160a01b03163b115b9695505050505050565b6001600160a01b0381168114611aa9575f5ffd5b50565b5f5f83601f840112611abc575f5ffd5b5081356001600160401b03811115611ad2575f5ffd5b602083019150836020828501011115611ae9575f5ffd5b9250929050565b5f5f5f5f5f60808688031215611b04575f5ffd5b8535611b0f81611a95565b9450602086013593506040860135925060608601356001600160401b03811115611b37575f5ffd5b611b4388828901611aac565b969995985093965092949392505050565b5f5f5f5f5f5f60a08789031215611b69575f5ffd5b8635611b7481611a95565b955060208701359450604087013593506060870135611b9281611a95565b925060808701356001600160401b03811115611bac575f5ffd5b611bb889828a01611aac565b979a9699509497509295939492505050565b5f5f5f5f5f5f5f5f60c0898b031215611be1575f5ffd5b883560ff81168114611bf1575f5ffd5b97506020890135611c0181611a95565b96506040890135611c1181611a95565b95506060890135945060808901356001600160401b03811115611c32575f5ffd5b611c3e8b828c01611aac565b90955093505060a08901356001600160401b03811115611c5c575f5ffd5b611c688b828c01611aac565b999c989b5096995094979396929594505050565b5f5f60208385031215611c8d575f5ffd5b82356001600160401b03811115611ca2575f5ffd5b611cae85828601611aac565b90969095509350505050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b5f5f83601f840112611cff575f5ffd5b5081356001600160401b03811115611d15575f5ffd5b6020830191508360208260051b8501011115611ae9575f5ffd5b5f5f5f5f5f5f5f5f5f60a08a8c031215611d47575f5ffd5b89356001600160401b03811115611d5c575f5ffd5b611d688c828d01611cef565b909a5098505060208a01356001600160401b03811115611d86575f5ffd5b611d928c828d01611cef565b90985096505060408a01356001600160401b03811115611db0575f5ffd5b611dbc8c828d01611cef565b90965094505060608a0135611dd081611a95565b925060808a01356001600160401b03811115611dea575f5ffd5b611df68c828d01611aac565b915080935050809150509295985092959850929598565b5f5f5f5f60608587031215611e20575f5ffd5b843593506020850135925060408501356001600160401b03811115611e43575f5ffd5b611e4f87828801611aac565b95989497509550505050565b5f5f5f5f5f5f5f5f6080898b031215611e72575f5ffd5b88356001600160401b03811115611e87575f5ffd5b611e938b828c01611cef565b90995097505060208901356001600160401b03811115611eb1575f5ffd5b611ebd8b828c01611cef565b90975095505060408901356001600160401b03811115611edb575f5ffd5b611ee78b828c01611cef565b90955093505060608901356001600160401b03811115611c5c575f5ffd5b80820180821115610a6b57634e487b7160e01b5f52601160045260245ffd5b818382375f9101908152919050565b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215611f57575f5ffd5b8135611f6281611a95565b9392505050565b6001600160a01b0386168152602081018590526080604082018190528101839052828460a08301375f60a084830101525f60a0601f19601f860116830101905060ff831660608301529695505050505050565b634e487b7160e01b5f52604160045260245ffd5b604051606081016001600160401b0381118282101715611ff257611ff2611fbc565b60405290565b604051608081016001600160401b0381118282101715611ff257611ff2611fbc565b604051601f8201601f191681016001600160401b038111828210171561204257612042611fbc565b604052919050565b5f6001600160401b0382111561206257612062611fbc565b50601f01601f191660200190565b5f5f60408385031215612081575f5ffd5b82518015158114612090575f5ffd5b60208401519092506001600160401b038111156120ab575f5ffd5b8301601f810185136120bb575f5ffd5b80516120ce6120c98261204a565b61201a565b8181528660208385010111156120e2575f5ffd5b8160208401602083015e5f602083830101528093505050509250929050565b5f6001600160401b0382111561211957612119611fbc565b5060051b60200190565b5f82601f830112612132575f5ffd5b81356121406120c982612101565b80828252602082019150602060608402860101925085831115612161575f5ffd5b602085015b838110156121c3576060818803121561217d575f5ffd5b612185611fd0565b813561219081611a95565b815260208201356121a081611a95565b602082810191909152604083810135908301529084529290920191606001612166565b5095945050505050565b5f82601f8301126121dc575f5ffd5b81356121ea6120c982612101565b8082825260208201915060208360051b86010192508583111561220b575f5ffd5b602085015b838110156121c357803561222381611a95565b835260209283019201612210565b5f5f5f60608486031215612243575f5ffd5b83356001600160401b03811115612258575f5ffd5b8401601f81018613612268575f5ffd5b80356122766120c982612101565b8082825260208201915060208360051b850101925088831115612297575f5ffd5b602084015b838110156123955780356001600160401b038111156122b9575f5ffd5b85016080818c03601f190112156122ce575f5ffd5b6122d6611ff8565b60208201356122e481611a95565b81526040820135602082015260608201356001600160401b03811115612308575f5ffd5b82016020810190603f018d1361231c575f5ffd5b803561232a6120c98261204a565b8181528e602083850101111561233e575f5ffd5b816020840160208301375f6020838301015280604085015250505060808201356001600160401b03811115612371575f5ffd5b6123808d602083860101612123565b6060830152508452506020928301920161229c565b50955050505060208401356001600160401b038111156123b3575f5ffd5b6123bf868287016121cd565b93969395505050506040919091013590565b5f82518060208501845e5f920191825250919050565b5f602082840312156123f7575f5ffd5b505191905056fea2646970667358221220708d92385dced02c45da822de4335dd13ef84d522ab9e3e089757cc9e1b1164d64736f6c63430008220033
Verified Source Code Partial Match
Compiler: v0.8.28+commit.7893614a
EVM: cancun
Optimization: Yes (200 runs)
FlashLoanRouter.sol 779 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title VANTAGE FlashLoanRouter
* @notice Gnosis Safe module + all-protocol flash loan receiver.
* @author gadget_saavy
* Protocol support table:
*
* Protocol | Primitive | Entry (we call) | Callback (called on us)
* --------------|----------------|-------------------------------------|------------------------------
* Aave V2 | Flash Loan | LendingPool.flashLoan(...) | executeOperation(address[],...)
* Aave V3 | Flash Loan | Pool.flashLoan(...) | executeOperation(address[],...)
* Aave V3 Simp | Flash Loan | Pool.flashLoanSimple(...) | executeOperation(address,...)
* Balancer V2 | Flash Loan | Vault.flashLoan(...) | receiveFlashLoan(...)
* Balancer V3 | Unlock Loan | Vault.unlock(...) | unlockCallback(bytes)
* Uniswap V2 | Flash Swap | Pair.swap(a,b,this,data) | uniswapV2Call(...)
* Uniswap V3 | Flash | Pool.flash(...) | uniswapV3FlashCallback(...)
* Uniswap V4 | Unlock Loan | PoolManager.unlock(...) | unlockCallback(bytes)
* SushiSwap | Flash Swap | Pair.swap(a,b,this,data) | uniswapV2Call(...)
* Curve | Swap only | Router.exchange(...) | (none — no flash primitive)
*
* Repayment per protocol:
* Aave V2/V3 → _approveMax(asset, pool, amount+premium) [pool pulls after callback returns]
* Balancer V2 → safeTransfer(vault, amount+fee) [vault checks balance after callback]
* Balancer V3 → settlement steps embedded in Step[] [vault accounting unlock model]
* Uniswap V2 → transfer(pair, repay) embedded in Step[] [pair checks invariant after callback]
* Uniswap V3 → transfer(pool, amount+fee) in Step[] [pool checks balance after callback]
* Uniswap V4 → settlement steps embedded in Step[] [PoolManager accounting unlock model]
* SushiSwap → transfer(pair, repay) embedded in Step[] [same as Uni V2]
*
* Callback payload format (abi.encode):
* (Step[], address[] sweepTokens, uint256 bribeWei)
*
* Author: gadget_saavy
*
* ─── EXECUTION FLOW [INVARIANT 2.1] ────────────────────────────────────────
* [01] Enabled via Safe.enableModule(thisContract)
* [02] executor calls executeArbitrage(kind, caller, target, value, callData, payload)
* [03] Module calls Safe.execTransactionFromModuleReturnData(target, ...)
* [04] Safe calls the protocol (Aave.flashLoan / Pair.swap / PoolManager.unlock / etc.)
* [05] Protocol executes and calls back into this contract
* [06] Callback: _validateCallback → _executeCalls → repay/approve → _disarm
* [07] Control returns through Safe back to executeArbitrage
* [08] _sweepProfits runs (all flash repayments are already settled at this point)
* ────────────────────────────────────────────────────────────────────────────
*
* ─── SAFE-PROXY SECURITY ────────────────────────────────────────────────────
* The Safe proxy is one-directional: we call IT (via execTransactionFromModule*).
* IT must never call OUR callbacks. Two guards enforce this:
* A) _arm() rejects protocolCaller == safe unconditionally.
* B) _validateCallback() hard-rejects msg.sender == safe before any whitelist check.
* This closes the attack vector where a Safe-signed transaction (bypassing our
* executeArbitrage entry guard) could reach our callback logic.
* ────────────────────────────────────────────────────────────────────────────
*/
// ─── Gnosis Safe interface ───────────────────────────────────────────────────
//
// execTransactionFromModuleReturnData is the correct function to use when we
// need revert reasons from inner calls to bubble up through the Safe.
// The older execTransactionFromModule drops revert data, making failures opaque.
//
interface ISafe {
/**
* @dev Execute a transaction from a module. Returns success/failure only.
* Revert data from the inner call is NOT accessible — use
* execTransactionFromModuleReturnData when you need the reason.
*/
function execTransactionFromModule(
address to,
uint256 value,
bytes memory data,
uint8 operation
) external returns (bool success);
/**
* @dev Execute a transaction from a module and return the inner call's
* full return / revert data. Use this in all production paths so
* protocol-level revert reasons surface to the caller.
*/
function execTransactionFromModuleReturnData(
address to,
uint256 value,
bytes memory data,
uint8 operation
) external returns (bool success, bytes memory returnData);
}
// ─── Contract ────────────────────────────────────────────────────────────────
contract FlashLoanRouter {
using SafeERC20 for IERC20;
// =========================================================================
// IMMUTABLES — set once at deployment, never change
// =========================================================================
address public immutable safe; // Gnosis Safe proxy this module is enabled on
address public immutable beneficiary; // Profits swept here after each execution
address public immutable executor; // Only address allowed to call executeArbitrage*
// ── Per-protocol flash provider addresses ────────────────────────────────
// Each is individually whitelisted as a valid callback sender.
// Zero address = not configured on this deployment (that callback will always
// reject because address(0) can never be msg.sender in the EVM).
address public immutable aaveV2Pool; // Aave V2 LendingPool
address public immutable aaveV3Pool; // Aave V3 Pool
address public immutable balancerV2Vault; // Balancer V2 Vault
address public immutable balancerV3Vault; // Balancer V3 Vault (unlock-based)
address public immutable uniV4PoolManager; // Uniswap V4 PoolManager (unlock-based)
// UniV2 / UniV3 / SushiSwap: pairs/pools are dynamic per execution.
// Validated via expectedCaller (set at _arm time to the specific pair/pool address).
// Curve: no flash primitive — swap-step only, validated in Python before submission.
// =========================================================================
// FLASH KIND CONSTANTS
// Must match FLASH_KIND_MAP in execution_manager.py exactly.
// =========================================================================
uint8 public constant FLASH_AAVE_V2 = 1;
uint8 public constant FLASH_AAVE_V3 = 2;
uint8 public constant FLASH_AAVE_V3_SIMPLE = 3;
uint8 public constant FLASH_BALANCER_V2 = 4;
uint8 public constant FLASH_BALANCER_V3 = 5;
uint8 public constant FLASH_UNI_V2 = 6;
uint8 public constant FLASH_UNI_V3 = 7;
uint8 public constant FLASH_UNI_V4 = 8;
uint8 public constant FLASH_SUSHI_V2 = 9;
uint8 private constant FLASH_KIND_MAX = 9;
// =========================================================================
// EPHEMERAL GUARDS — armed per-execution, disarmed in callback
// =========================================================================
address private _expectedCaller; // Protocol/pair we expect to call us back
bytes32 private _expectedPayloadHash; // keccak256 of callbackPayload; tamper guard
uint8 private _activeFlashKind; // Which flash primitive is active
bool private _active; // True only inside an active execution window
bool private _executing; // True while _executeCalls is running (blocks nested callbacks)
// =========================================================================
// REENTRANCY GUARD
// =========================================================================
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
// =========================================================================
// STRUCTS
// =========================================================================
/// @dev Token approval executed before a step's call.
struct Approval {
address token; // ERC20 address; address(0) = native ETH, skip
address spender; // Allowance target (never address(this))
uint256 minRequired; // Approve type(uint256).max if allowance < minRequired
}
/// @dev One atomic execution step inside the flash callback.
struct Step {
address target;
uint256 value;
bytes callData;
Approval[] approvals; // Run in order before target.call
}
// =========================================================================
// EVENTS
// =========================================================================
event ArbitrageExecuted(
address indexed safe,
bytes32 indexed payloadHash,
uint8 indexed flashKind
);
event Sweep(address indexed token, uint256 amount);
// =========================================================================
// CUSTOM ERRORS
// ─── Access / state ───────────────────────────────────────────────────────
error Unauthorized(); // msg.sender is not executor (entry points)
error AlreadyEntered(); // Reentrancy guard tripped
error NotArmed(); // Callback arrived with _active == false
error PayloadMismatch(); // keccak256(payload) != _expectedPayloadHash
error SafeCallbackBlocked(); // Safe proxy attempted to call a callback
error BadCaller(); // msg.sender not in protocol whitelist
error BadInitiator(); // Aave initiator is not safe (Safe path) or address(this) (direct path)
error NestedCallback(); // _executeCalls called while already executing steps
error CallbackNotFired(); // Protocol returned ok but _active still set
error InvalidFlashKind(); // kind == 0 or > FLASH_KIND_MAX
error CallerIsSafe(); // protocolCaller == safe passed to _arm
// ─── Targets / spenders ───────────────────────────────────────────────────
error BadTarget(uint256 step); // Step targets zero address, this contract, or safe
error BadSpender(); // Approval spender is zero or this contract
// ─── Call failures ────────────────────────────────────────────────────────
// NOTE: when inner calls fail WITH revert data, the data is bubbled via
// assembly inline revert — the typed errors below are only emitted when
// there is NO revert data (e.g. out-of-gas in the callee).
error ProtocolCallFailed(); // execTransactionFromModuleReturnData returned false, no data
error DirectCallFailed(); // direct protocolTarget.call failed, no data
error StepFailed(uint256 step); // step.call failed, no data
// ─── Financial ────────────────────────────────────────────────────────────
error BribeFailed();
error EthSweepFailed();
// ─── Constructor ──────────────────────────────────────────────────────────
error ZeroAddress(string field);
error AddressConflict(string field);
// =========================================================================
// MODIFIERS
// =========================================================================
modifier nonReentrant() {
if (_status == _ENTERED) revert AlreadyEntered();
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
modifier onlyExecutor() {
if (msg.sender != executor) revert Unauthorized();
_;
}
// =========================================================================
// CONSTRUCTOR
// =========================================================================
constructor(
address _safe,
address _beneficiary,
address _executor,
address _aaveV2Pool,
address _aaveV3Pool,
address _balancerV2Vault,
address _balancerV3Vault,
address _uniV4PoolManager
) {
// ── Zero-address guards ──────────────────────────────────────────────
if (_safe == address(0)) revert ZeroAddress("safe");
if (_beneficiary == address(0)) revert ZeroAddress("beneficiary");
if (_executor == address(0)) revert ZeroAddress("executor");
// ── Conflict guards ──────────────────────────────────────────────────
// executor is the trusted EOA; must be separate from everything.
if (_executor == _safe) revert AddressConflict("executor==safe");
if (_executor == address(this)) revert AddressConflict("executor==this");
if (_executor == _beneficiary) revert AddressConflict("executor==beneficiary");
// safe (the proxy) must be a distinct contract from this module.
if (_safe == address(this)) revert AddressConflict("safe==this");
// beneficiary must not be this contract (funds would be locked forever).
// beneficiary == safe is allowed: profits sweeping to the Safe is valid.
if (_beneficiary == address(this)) revert AddressConflict("beneficiary==this");
// Protocol addresses are allowed to be zero if not used on this chain.
safe = _safe;
beneficiary = _beneficiary;
executor = _executor;
aaveV2Pool = _aaveV2Pool;
aaveV3Pool = _aaveV3Pool;
balancerV2Vault = _balancerV2Vault;
balancerV3Vault = _balancerV3Vault;
uniV4PoolManager = _uniV4PoolManager;
_status = _NOT_ENTERED;
}
receive() external payable {}
// =========================================================================
// [A] EXECUTOR ENTRY POINTS
// =========================================================================
/**
* @notice Primary execution path. Routes protocol call through the Gnosis Safe.
*
* Uses execTransactionFromModuleReturnData so that any revert from the
* protocol or from our own callback bubbles up as the real revert reason,
* rather than being swallowed by the Safe.
*
* @param kind Protocol constant (FLASH_AAVE_V2 … FLASH_SUSHI_V2).
* @param protocolCaller Address that will invoke our callback (set as _expectedCaller).
* For fixed-pool protocols (Aave V2/V3, Balancer V2, Uni V4):
* same as protocolTarget.
* For dynamic-pool protocols (UniV2/V3/Sushi pair/pool):
* the specific pair or pool address for this trade.
* MUST NOT be the Safe address.
* @param protocolTarget Contract the Safe will call (Aave pool, Balancer vault, etc.).
* MUST NOT be address(this) or address(0).
* @param value ETH value forwarded in the protocol call.
* @param protocolCallData ABI-encoded protocol entry (flashLoan / swap / unlock / etc.).
* @param callbackPayload abi.encode(Step[], address[] sweepTokens, uint256 bribeWei).
*/
function executeArbitrage(
uint8 kind,
address protocolCaller,
address protocolTarget,
uint256 value,
bytes calldata protocolCallData,
bytes calldata callbackPayload
) external onlyExecutor nonReentrant returns (bool) {
// Sanity checks on the target before arming.
// Safe is explicitly blocked: the relationship is one-directional — we call the
// Safe, the Safe calls the protocol. Routing a protocol call back into the Safe
// would create a recursive/confusing execution trace.
if (
protocolTarget == address(0) ||
protocolTarget == address(this) ||
protocolTarget == safe
) revert BadTarget(0);
_arm(kind, protocolCaller, callbackPayload);
// Route through the Safe. execTransactionFromModuleReturnData gives us the
// inner call's return/revert data so failures are surfaced, not swallowed.
(bool ok, bytes memory retData) = ISafe(safe).execTransactionFromModuleReturnData(
protocolTarget,
value,
protocolCallData,
0 // Enum.Operation.Call
);
if (!ok) {
// Bubble up the revert reason from inside the protocol call or our callback.
// If the protocol reverted with a message, this surfaces it exactly.
_bubble(retData, abi.encodeWithSelector(ProtocolCallFailed.selector));
}
// The callback MUST have disarmed by now. If still armed, the flash
// provider returned without calling our callback — bad calldata or
// mismatched kind.
if (_active) revert CallbackNotFired();
// Sweep profits AFTER flash settlement is fully complete.
_sweepProfits(callbackPayload);
return true;
}
/**
* @notice Alternate execution path. Calls the protocol directly (not via Safe).
*
* Used when this contract itself must be the initiator (e.g. certain unlock
* flows where the Safe cannot be the outer caller). All security invariants
* are identical to executeArbitrage; only the call routing differs.
*/
function executeArbitrageDirectly(
uint8 kind,
address protocolCaller,
address protocolTarget,
uint256 value,
bytes calldata protocolCallData,
bytes calldata callbackPayload
) external onlyExecutor nonReentrant returns (bool) {
if (
protocolTarget == address(0) ||
protocolTarget == address(this) ||
protocolTarget == safe
) revert BadTarget(0);
_arm(kind, protocolCaller, callbackPayload);
(bool ok, bytes memory retData) = protocolTarget.call{value: value}(protocolCallData);
if (!ok) {
_bubble(retData, abi.encodeWithSelector(DirectCallFailed.selector));
}
if (_active) revert CallbackNotFired();
_sweepProfits(callbackPayload);
return true;
}
// =========================================================================
// [B] AAVE V2 + AAVE V3 — executeOperation (array / multi-asset form)
//
// Aave V2 LendingPool and Aave V3 Pool share the IDENTICAL callback signature.
// Differentiated at runtime by msg.sender:
// msg.sender == aaveV2Pool → Aave V2 LendingPool
// msg.sender == aaveV3Pool → Aave V3 Pool
//
// Repayment: _approveMax(asset, pool, amount+premium) for each asset.
// Aave pulls via transferFrom AFTER our callback returns true.
// Sweep runs in executeArbitrage after the Safe call completes
// (i.e. after Aave has already pulled its repayment).
// =========================================================================
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool) {
_validateCallback(params);
// Exactly one of the two Aave pools may call this, and only when armed for V2 or V3.
if (msg.sender != aaveV2Pool && msg.sender != aaveV3Pool) revert BadCaller();
if (_activeFlashKind != FLASH_AAVE_V2 && _activeFlashKind != FLASH_AAVE_V3)
revert InvalidFlashKind();
// Initiator is whoever called Aave's flashLoan:
// executeArbitrage path → Safe called flashLoan → initiator == safe
// executeArbitrageDirectly path → module called flashLoan → initiator == address(this)
// executor EOA path → EOA called flashLoan → initiator == executor
if (initiator != safe && initiator != address(this) && initiator != executor)
revert BadInitiator();
_executeCalls(params);
// Approve the calling pool to pull repayment.
// Aave V2/V3 both pull via transferFrom AFTER this callback returns.
address callingPool = msg.sender;
for (uint256 i = 0; i < assets.length; i++) {
_approveMax(assets[i], callingPool, amounts[i] + premiums[i]);
}
_disarm();
return true;
}
// =========================================================================
// [C] AAVE V3 ONLY — executeOperation (single-asset / flashLoanSimple form)
//
// Aave V2 does not have flashLoanSimple; this overload is V3-exclusive.
// Repayment: _approveMax(asset, aaveV3Pool, amount+premium).
// =========================================================================
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external returns (bool) {
_validateCallback(params);
if (msg.sender != aaveV3Pool) revert BadCaller();
if (_activeFlashKind != FLASH_AAVE_V3_SIMPLE) revert InvalidFlashKind();
if (initiator != safe && initiator != address(this) && initiator != executor)
revert BadInitiator();
_executeCalls(params);
_approveMax(asset, aaveV3Pool, amount + premium);
_disarm();
return true;
}
// =========================================================================
// [D] BALANCER V2 — receiveFlashLoan
//
// Vault.flashLoan(recipient, tokens[], amounts[], userData) calls this.
// Repayment: safeTransfer(vault, amount + feeAmount) for each token.
// Vault re-checks its own balance after this callback returns;
// insufficient transfer → Vault reverts with BAL#xxx.
// =========================================================================
function receiveFlashLoan(
IERC20[] calldata tokens,
uint256[] calldata amounts,
uint256[] calldata feeAmounts,
bytes calldata userData
) external {
_validateCallback(userData);
if (msg.sender != balancerV2Vault) revert BadCaller();
if (_activeFlashKind != FLASH_BALANCER_V2) revert InvalidFlashKind();
_executeCalls(userData);
for (uint256 i = 0; i < tokens.length; i++) {
tokens[i].safeTransfer(balancerV2Vault, amounts[i] + feeAmounts[i]);
}
_disarm();
// Sweep runs in executeArbitrage after Balancer re-validates its vault balance.
}
// =========================================================================
// [E] BALANCER V3 + UNISWAP V4 — unlockCallback
//
// Both Balancer V3 Vault and Uniswap V4 PoolManager use unlockCallback(bytes).
// Differentiated by msg.sender:
// msg.sender == balancerV3Vault → Balancer V3 unlock
// msg.sender == uniV4PoolManager → Uniswap V4 unlock
//
// Settlement is EMBEDDED in Step[]:
// Balancer V3: steps include vault.sendTo() + vault.settle()
// Uniswap V4: steps include poolManager.take() + poolManager.settle()
// The off-chain encoder is solely responsible for constructing correct
// settlement steps. This contract only executes what it is given.
// =========================================================================
function unlockCallback(bytes calldata data)
external
returns (bytes memory)
{
_validateCallback(data);
if (msg.sender != balancerV3Vault && msg.sender != uniV4PoolManager)
revert BadCaller();
if (_activeFlashKind != FLASH_BALANCER_V3 && _activeFlashKind != FLASH_UNI_V4)
revert InvalidFlashKind();
_executeCalls(data);
_disarm();
return bytes("");
}
// =========================================================================
// [F] UNISWAP V2 / SUSHISWAP — uniswapV2Call
//
// Triggered by Pair.swap(amount0Out, amount1Out, recipient=this, data) when
// data is non-empty. msg.sender = the specific pair (set as _expectedCaller
// at _arm time).
//
// `sender` param = the address that called Pair.swap:
// executeArbitrage path → Safe called swap → sender == safe
// executeArbitrageDirectly → module called swap → sender == address(this)
// executor EOA → EOA called swap → sender == executor
//
// Repayment: embedded as the last Step in the payload. The off-chain encoder
// calculates repayAmount = ceil(borrowed * 1000 / 997) and appends a
// transfer(pair, repayAmount) step. The pair checks its own invariant
// AFTER this callback returns.
// =========================================================================
function uniswapV2Call(
address sender,
uint256 /*amount0*/,
uint256 /*amount1*/,
bytes calldata data
) external {
_validateCallback(data);
// msg.sender must be the exact pair we armed against
if (msg.sender != _expectedCaller) revert BadCaller();
if (_activeFlashKind != FLASH_UNI_V2 && _activeFlashKind != FLASH_SUSHI_V2)
revert InvalidFlashKind();
// sender is whoever called pair.swap — module (direct), Safe (via route), or executor EOA
if (sender != safe && sender != address(this) && sender != executor) revert BadCaller();
// Repayment transfer is the final step; _executeCalls handles it
_executeCalls(data);
_disarm();
}
// =========================================================================
// [G] UNISWAP V3 — uniswapV3FlashCallback
//
// Triggered by Pool.flash(recipient=this, amount0, amount1, data).
// msg.sender = the specific pool (set as _expectedCaller at _arm time).
//
// fee0 / fee1 are informational; the off-chain encoder pre-computes
// repayN = amountN + ceil(amountN * feeTier / 1e6) and embeds a
// transfer(pool, repayN) step for each non-zero amount.
// The pool checks token balances AFTER this callback returns.
// =========================================================================
function uniswapV3FlashCallback(
uint256 /*fee0*/,
uint256 /*fee1*/,
bytes calldata data
) external {
_validateCallback(data);
if (msg.sender != _expectedCaller) revert BadCaller();
if (_activeFlashKind != FLASH_UNI_V3) revert InvalidFlashKind();
_executeCalls(data);
_disarm();
}
// =========================================================================
// INTERNAL — ARM
//
// Sets all ephemeral guards before the protocol call is made.
// Validated on every entry to a callback via _validateCallback.
// =========================================================================
function _arm(
uint8 kind,
address caller,
bytes calldata payload
) internal {
if (kind == 0 || kind > FLASH_KIND_MAX) revert InvalidFlashKind();
// The Safe proxy must NEVER be the expected callback source.
// The Safe is our outbound relay — the relationship is strictly:
// executeArbitrage → Safe.execTransactionFromModuleReturnData → protocol → callback
// If caller == safe, any Safe-signed transaction could trigger our
// callbacks without going through executeArbitrage first.
if (caller == safe) revert CallerIsSafe();
_activeFlashKind = kind;
_expectedCaller = caller;
_expectedPayloadHash = keccak256(payload);
_active = true;
}
// =========================================================================
// INTERNAL — VALIDATE CALLBACK
//
// Called at the top of EVERY callback handler. Enforces:
// 1. Execution window is active (armed by executeArbitrage).
// 2. Safe proxy is unconditionally rejected as callback source —
// defense-in-depth even if some code path were to set _expectedCaller
// incorrectly, the Safe can never pass this gate.
// 3. msg.sender is in the static protocol whitelist OR is the dynamic
// _expectedCaller (for pair/pool-level protocols).
// 4. Payload hash matches what was committed at arm time.
//
// Individual callbacks may add further per-function sender checks.
// =========================================================================
function _validateCallback(bytes calldata payload) internal view {
// Gate 1 — execution window
if (!_active) revert NotArmed();
// Gate 2 — hard-block Safe proxy, unconditionally and first
if (msg.sender == safe) revert SafeCallbackBlocked();
// Gate 3 — whitelist: static protocol addresses + dynamic _expectedCaller
bool validSender = (
msg.sender == aaveV2Pool ||
msg.sender == aaveV3Pool ||
msg.sender == balancerV2Vault ||
msg.sender == balancerV3Vault ||
msg.sender == uniV4PoolManager ||
msg.sender == _expectedCaller
);
if (!validSender) revert BadCaller();
// Gate 4 — payload integrity
if (keccak256(payload) != _expectedPayloadHash) revert PayloadMismatch();
}
// =========================================================================
// INTERNAL — APPROVE MAX (SafeERC20 + USDT-safe)
// =========================================================================
function _approveMax(
address token,
address spender,
uint256 minRequired
) internal {
if (token == address(0)) return; // native ETH — no approval needed
if (spender == address(0)) revert BadSpender();
if (spender == address(this)) revert BadSpender(); // self-approval is always a bug
IERC20 t = IERC20(token);
if (t.allowance(address(this), spender) >= minRequired) return;
// forceApprove handles USDT's non-standard reset-to-zero requirement
t.forceApprove(spender, type(uint256).max);
}
// =========================================================================
// INTERNAL — EXECUTE CALLS
//
// Decodes Step[] from the callback payload and runs each step in sequence:
// 1. Per-step approvals via _approveMax (before the call).
// 2. Direct call from this module — module is msg.sender.
// Funds (flash-borrowed tokens) sit in this module; DEX routers/pools
// pull them via transferFrom using allowances set in step 1.
// 3. On failure WITH revert data → inline assembly revert (bubbles reason)
// On failure WITHOUT revert data → typed StepFailed(i) error
//
// Why direct calls and NOT via Safe.execTransactionFromModule:
// The protocol already called this callback because Safe called the protocol
// (step [03] in the execution flow). Inside the callback the module is the
// active context and holds all flash-borrowed tokens. Routing swap steps back
// through the Safe would make the Safe msg.sender for DEX calls, but DEX
// routers use msg.sender as the token-pull address — not this module — which
// breaks transferFrom since the Safe has no tokens and no approvals.
//
// Does NOT disarm or sweep — those happen in the calling callback.
// =========================================================================
function _executeCalls(bytes calldata params) internal {
if (_executing) revert NestedCallback();
_executing = true;
(Step[] memory steps, , ) = abi.decode(params, (Step[], address[], uint256));
uint256 n = steps.length;
for (uint256 i = 0; i < n; i++) {
Step memory s = steps[i];
// Never allow a step to call zero address, this contract, or the Safe.
if (
s.target == address(0) ||
s.target == address(this) ||
s.target == safe
) revert BadTarget(i);
// Run per-step approvals before the call
uint256 m = s.approvals.length;
for (uint256 j = 0; j < m; j++) {
_approveMax(
s.approvals[j].token,
s.approvals[j].spender,
s.approvals[j].minRequired
);
}
// Execute directly from this module; bubble any revert reason
(bool ok, bytes memory retData) = s.target.call{value: s.value}(s.callData);
if (!ok) {
_bubble(retData, abi.encodeWithSelector(StepFailed.selector, i));
}
}
_executing = false;
}
// =========================================================================
// INTERNAL — DISARM
// =========================================================================
function _disarm() internal {
_active = false;
emit ArbitrageExecuted(safe, _expectedPayloadHash, _activeFlashKind);
}
// =========================================================================
// INTERNAL — SWEEP PROFITS + MEV BRIBE
//
// Runs in executeArbitrage* AFTER the Safe call returns and all flash
// settlements are complete:
// Aave — pool already pulled repayment via transferFrom
// Balancer — we transferred amount+fee inside receiveFlashLoan
// Others — settlement embedded in steps, finished before callback returned
//
// Order: bribe first (tips the builder), then ERC20s, then any ETH residue.
// =========================================================================
function _sweepProfits(bytes calldata params) internal {
(, address[] memory sweepTokens, uint256 bribe) =
abi.decode(params, (Step[], address[], uint256));
// MEV builder tip — pay coinbase before anything else
if (bribe > 0) {
(bool bribed, ) = block.coinbase.call{value: bribe}("");
if (!bribed) revert BribeFailed();
}
// ERC20 profit sweep
uint256 n = sweepTokens.length;
for (uint256 i = 0; i < n; i++) {
address token = sweepTokens[i];
if (token == address(0)) continue;
uint256 bal = IERC20(token).balanceOf(address(this));
if (bal > 0) {
IERC20(token).safeTransfer(beneficiary, bal);
emit Sweep(token, bal);
}
}
// ETH residue sweep
uint256 ethBal = address(this).balance;
if (ethBal > 0) {
(bool sent, ) = payable(beneficiary).call{value: ethBal}("");
if (!sent) revert EthSweepFailed();
}
}
// =========================================================================
// INTERNAL — BUBBLE REVERT DATA
//
// If `data` is non-empty, re-revert with it verbatim so the caller sees the
// original reason (whether it's a string, a custom error, or a panic code).
// If `data` is empty (out-of-gas or similar), revert with `fallback`.
// =========================================================================
function _bubble(bytes memory data, bytes memory fallback_) internal pure {
if (data.length > 0) {
assembly {
revert(add(data, 32), mload(data))
}
}
assembly {
revert(add(fallback_, 32), mload(fallback_))
}
}
}
SafeERC20.sol 212 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
IERC1363.sol 86 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";
IERC20.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
Read Contract
FLASH_AAVE_V2 0xa84f29dd → uint8
FLASH_AAVE_V3 0x30fd3da6 → uint8
FLASH_AAVE_V3_SIMPLE 0x8ec4512d → uint8
FLASH_BALANCER_V2 0xa5871a5f → uint8
FLASH_BALANCER_V3 0xd4fff981 → uint8
FLASH_SUSHI_V2 0x5235aa99 → uint8
FLASH_UNI_V2 0x4f716ec9 → uint8
FLASH_UNI_V3 0x66e21b47 → uint8
FLASH_UNI_V4 0x83236ac2 → uint8
aaveV2Pool 0x202a5366 → address
aaveV3Pool 0x86a06ff0 → address
balancerV2Vault 0x10c0a157 → address
balancerV3Vault 0x0acefd35 → address
beneficiary 0x38af3eed → address
executor 0xc34c08e5 → address
safe 0x186f0354 → address
uniV4PoolManager 0x47ca5a42 → address
Write Contract 8 functions
These functions modify contract state and require a wallet transaction to execute.
executeArbitrage 0x9fee4c4e
uint8 kind
address protocolCaller
address protocolTarget
uint256 value
bytes protocolCallData
bytes callbackPayload
returns: bool
executeArbitrageDirectly 0x26e56de8
uint8 kind
address protocolCaller
address protocolTarget
uint256 value
bytes protocolCallData
bytes callbackPayload
returns: bool
executeOperation 0x1b11d0ff
address asset
uint256 amount
uint256 premium
address initiator
bytes params
returns: bool
executeOperation 0x920f5c84
address[] assets
uint256[] amounts
uint256[] premiums
address initiator
bytes params
returns: bool
receiveFlashLoan 0xf04f2707
address[] tokens
uint256[] amounts
uint256[] feeAmounts
bytes userData
uniswapV2Call 0x10d1e85c
address sender
uint256
uint256
bytes data
uniswapV3FlashCallback 0xe9cbafb0
uint256
uint256
bytes data
unlockCallback 0x91dd7346
bytes data
returns: bytes
Recent Transactions
This address has 1 on-chain transactions, but only 1.4% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →