Forkchoice Ethereum Mainnet

Address Contract Partially Verified

Address 0x9f004Dae0487f9F82a5Fa2a1500d53b486e42350
Balance 0 ETH
Nonce 1
Code Size 9268 bytes
Indexed Transactions 0 (1 on-chain, 1.4% indexed)
External Etherscan · Sourcify

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 →