diff --git a/crates/autopilot/src/on_settlement_event_updater.rs b/crates/autopilot/src/on_settlement_event_updater.rs index e6091d2ddb..6a96c8eb15 100644 --- a/crates/autopilot/src/on_settlement_event_updater.rs +++ b/crates/autopilot/src/on_settlement_event_updater.rs @@ -195,7 +195,7 @@ impl OnSettlementEventUpdater { .collect(), }); } - Err(err) if matches!(err, DecodingError::InvalidSelector) => { + Err(DecodingError::InvalidSelector) => { // we indexed a transaction initiated by solver, that was not a settlement // for this case we want to have the entry in observations table but with zeros update.auction_data = Some(Default::default()); diff --git a/crates/contracts/artifacts/AnyoneAuthenticator.json b/crates/contracts/artifacts/AnyoneAuthenticator.json new file mode 100644 index 0000000000..6924aeb37a --- /dev/null +++ b/crates/contracts/artifacts/AnyoneAuthenticator.json @@ -0,0 +1 @@ +{"abi":[{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isSolver","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50609a8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806302cc250d14602d575b600080fd5b603e60383660046052565b50600190565b604051901515815260200160405180910390f35b600060208284031215606357600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114608657600080fd5b939250505056fea164736f6c6343000811000a","deployedBytecode":"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806302cc250d14602d575b600080fd5b603e60383660046052565b50600190565b604051901515815260200160405180910390f35b600060208284031215606357600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114608657600080fd5b939250505056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Swapper.json b/crates/contracts/artifacts/Swapper.json new file mode 100644 index 0000000000..3b4420dd93 --- /dev/null +++ b/crates/contracts/artifacts/Swapper.json @@ -0,0 +1 @@ +{"abi":[{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlement","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct Asset","name":"sell","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct Asset","name":"buy","type":"tuple"},{"components":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct Allowance","name":"allowance","type":"tuple"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Interaction","name":"call","type":"tuple"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506111c8806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80631626ba7e1461003b5780639ed280dd146100a7575b600080fd5b610071610049366004610b03565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b6100ba6100b5366004610bbc565b6100c8565b60405190815260200161009e565b6000602085018035906100db9087610c49565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610147573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016b9190610c66565b1015610179575060006108b8565b6102178673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101eb9190610c7f565b60006101fa6020890189610c49565b73ffffffffffffffffffffffffffffffffffffffff1691906108c1565b61029a8673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610265573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102899190610c7f565b602087018035906101fa9089610c49565b6040805160028082526060820183526000926020830190803683370190505090506102c86020870187610c49565b816000815181106102db576102db610ccb565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910182015261030b90860186610c49565b8160018151811061031e5761031e610ccb565b73ffffffffffffffffffffffffffffffffffffffff92909216602092830291909101820152604080516002808252606082018352600093919290918301908036833701905050905085602001358160008151811061037e5761037e610ccb565b6020026020010181815250508660200135816001815181106103a2576103a2610ccb565b6020908102919091010152604080516001808252818301909252600091816020015b6104406040518061016001604052806000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600063ffffffff16815260200160008019168152602001600081526020016000815260200160008152602001606081525090565b8152602001906001900390816103c45790505090506040518061016001604052806000815260200160018152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001896020013581526020018860200135815260200163ffffffff801681526020016000801b815260200160008152602001604081526020016000815260200130604051602001610506919060609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815260140190565b6040516020818303038152906040528152508160008151811061052b5761052b610ccb565b602002602001018190525061053e610adc565b60208088013590610551908b018b610c49565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e8c61057a60208c018c610c49565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061060e9190610c66565b101561078c5760408051600180825281830190925290816020015b60408051606080820183526000808352602083015291810191909152815260200190600190039081610629575050815261066660208a018a610c49565b8151805160009061067957610679610ccb565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff9092169091526106ac908a018a610c49565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b36106d460208a018a610c49565b60405173ffffffffffffffffffffffffffffffffffffffff909116602482015260208a01356044820152606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b929092179091529050816000602002015160008151811061077c5761077c610ccb565b6020026020010151604001819052505b60408051600180825281830190925290816020015b604080516060808201835260008083526020830152918101919091528152602001906001900390816107a157505060208201526107dd86610d72565b602082015180516000906107f3576107f3610ccb565b60200260200101819052506108b18a73ffffffffffffffffffffffffffffffffffffffff166313d79a0b868686866040516024016108349493929190611071565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b9290921790915273ffffffffffffffffffffffffffffffffffffffff8d16915061099c565b9450505050505b95945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b30000000000000000000000000000000000000000000000000000000017905290600090610954908616836109b1565b9050610995816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506109bf565b5050505050565b60006109aa83600084610a23565b9392505050565b60606109aa83600084610a56565b815115806109dc5750818060200190518101906109dc919061112a565b8190610a1e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a15919061114c565b60405180910390fd5b505050565b60005a905060008083516020850186885af1610a43573d6000803e3d6000fd5b5a610a4e908261115f565b949350505050565b606060008473ffffffffffffffffffffffffffffffffffffffff168484604051610a80919061119f565b60006040518083038185875af1925050503d8060008114610abd576040519150601f19603f3d011682016040523d82523d6000602084013e610ac2565b606091505b509250905080610ad457815160208301fd5b509392505050565b60405180606001604052806003905b6060815260200190600190039081610aeb5790505090565b600080600060408486031215610b1857600080fd5b83359250602084013567ffffffffffffffff80821115610b3757600080fd5b818601915086601f830112610b4b57600080fd5b813581811115610b5a57600080fd5b876020828501011115610b6c57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610ba157600080fd5b50565b600060408284031215610bb657600080fd5b50919050565b60008060008060006101008688031215610bd557600080fd5b8535610be081610b7f565b9450610bef8760208801610ba4565b9350610bfe8760608801610ba4565b9250610c0d8760a08801610ba4565b915060e086013567ffffffffffffffff811115610c2957600080fd5b860160608189031215610c3b57600080fd5b809150509295509295909350565b600060208284031215610c5b57600080fd5b81356109aa81610b7f565b600060208284031215610c7857600080fd5b5051919050565b600060208284031215610c9157600080fd5b81516109aa81610b7f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6040516060810167ffffffffffffffff81118282101715610d1d57610d1d610c9c565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d6a57610d6a610c9c565b604052919050565b600060608236031215610d8457600080fd5b610d8c610cfa565b8235610d9781610b7f565b815260208381013581830152604084013567ffffffffffffffff80821115610dbe57600080fd5b9085019036601f830112610dd157600080fd5b813581811115610de357610de3610c9c565b610e13847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610d23565b91508082523684828501011115610e2957600080fd5b808484018584013760009082019093019290925250604082015292915050565b60005b83811015610e64578181015183820152602001610e4c565b50506000910152565b60008151808452610e85816020860160208601610e49565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015610f975782840389528151610160815186528682015187870152604080830151610f1a8289018273ffffffffffffffffffffffffffffffffffffffff169052565b5050606082810151908701526080808301519087015260a08083015163ffffffff169087015260c0808301519087015260e080830151908701526101008083015190870152610120808301519087015261014091820151918601819052610f8381870183610e6d565b9a87019a9550505090840190600101610ed5565b5091979650505050505050565b6000826060808201846000805b6003811015610f97578584038952825180518086526020918201918087019190600582901b88018101865b8381101561105a578982037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00185528551805173ffffffffffffffffffffffffffffffffffffffff16835283810151848401526040908101519083018c90526110478c840182610e6d565b9684019695840195925050600101610fdc565b509c81019c97509590950194505050600101610fb1565b6080808252855190820181905260009060209060a0840190828901845b828110156110c057815173ffffffffffffffffffffffffffffffffffffffff168452928401929084019060010161108e565b5050508381038285015286518082528783019183019060005b818110156110f5578351835292840192918401916001016110d9565b505084810360408601526111098188610eb7565b92505050828103606084015261111f8185610fa4565b979650505050505050565b60006020828403121561113c57600080fd5b815180151581146109aa57600080fd5b6020815260006109aa6020830184610e6d565b81810381811115611199577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600082516111b1818460208701610e49565b919091019291505056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80631626ba7e1461003b5780639ed280dd146100a7575b600080fd5b610071610049366004610b03565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b6100ba6100b5366004610bbc565b6100c8565b60405190815260200161009e565b6000602085018035906100db9087610c49565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610147573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016b9190610c66565b1015610179575060006108b8565b6102178673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101eb9190610c7f565b60006101fa6020890189610c49565b73ffffffffffffffffffffffffffffffffffffffff1691906108c1565b61029a8673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610265573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102899190610c7f565b602087018035906101fa9089610c49565b6040805160028082526060820183526000926020830190803683370190505090506102c86020870187610c49565b816000815181106102db576102db610ccb565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910182015261030b90860186610c49565b8160018151811061031e5761031e610ccb565b73ffffffffffffffffffffffffffffffffffffffff92909216602092830291909101820152604080516002808252606082018352600093919290918301908036833701905050905085602001358160008151811061037e5761037e610ccb565b6020026020010181815250508660200135816001815181106103a2576103a2610ccb565b6020908102919091010152604080516001808252818301909252600091816020015b6104406040518061016001604052806000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600063ffffffff16815260200160008019168152602001600081526020016000815260200160008152602001606081525090565b8152602001906001900390816103c45790505090506040518061016001604052806000815260200160018152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001896020013581526020018860200135815260200163ffffffff801681526020016000801b815260200160008152602001604081526020016000815260200130604051602001610506919060609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815260140190565b6040516020818303038152906040528152508160008151811061052b5761052b610ccb565b602002602001018190525061053e610adc565b60208088013590610551908b018b610c49565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e8c61057a60208c018c610c49565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061060e9190610c66565b101561078c5760408051600180825281830190925290816020015b60408051606080820183526000808352602083015291810191909152815260200190600190039081610629575050815261066660208a018a610c49565b8151805160009061067957610679610ccb565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff9092169091526106ac908a018a610c49565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b36106d460208a018a610c49565b60405173ffffffffffffffffffffffffffffffffffffffff909116602482015260208a01356044820152606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b929092179091529050816000602002015160008151811061077c5761077c610ccb565b6020026020010151604001819052505b60408051600180825281830190925290816020015b604080516060808201835260008083526020830152918101919091528152602001906001900390816107a157505060208201526107dd86610d72565b602082015180516000906107f3576107f3610ccb565b60200260200101819052506108b18a73ffffffffffffffffffffffffffffffffffffffff166313d79a0b868686866040516024016108349493929190611071565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b9290921790915273ffffffffffffffffffffffffffffffffffffffff8d16915061099c565b9450505050505b95945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b30000000000000000000000000000000000000000000000000000000017905290600090610954908616836109b1565b9050610995816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506109bf565b5050505050565b60006109aa83600084610a23565b9392505050565b60606109aa83600084610a56565b815115806109dc5750818060200190518101906109dc919061112a565b8190610a1e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a15919061114c565b60405180910390fd5b505050565b60005a905060008083516020850186885af1610a43573d6000803e3d6000fd5b5a610a4e908261115f565b949350505050565b606060008473ffffffffffffffffffffffffffffffffffffffff168484604051610a80919061119f565b60006040518083038185875af1925050503d8060008114610abd576040519150601f19603f3d011682016040523d82523d6000602084013e610ac2565b606091505b509250905080610ad457815160208301fd5b509392505050565b60405180606001604052806003905b6060815260200190600190039081610aeb5790505090565b600080600060408486031215610b1857600080fd5b83359250602084013567ffffffffffffffff80821115610b3757600080fd5b818601915086601f830112610b4b57600080fd5b813581811115610b5a57600080fd5b876020828501011115610b6c57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610ba157600080fd5b50565b600060408284031215610bb657600080fd5b50919050565b60008060008060006101008688031215610bd557600080fd5b8535610be081610b7f565b9450610bef8760208801610ba4565b9350610bfe8760608801610ba4565b9250610c0d8760a08801610ba4565b915060e086013567ffffffffffffffff811115610c2957600080fd5b860160608189031215610c3b57600080fd5b809150509295509295909350565b600060208284031215610c5b57600080fd5b81356109aa81610b7f565b600060208284031215610c7857600080fd5b5051919050565b600060208284031215610c9157600080fd5b81516109aa81610b7f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6040516060810167ffffffffffffffff81118282101715610d1d57610d1d610c9c565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d6a57610d6a610c9c565b604052919050565b600060608236031215610d8457600080fd5b610d8c610cfa565b8235610d9781610b7f565b815260208381013581830152604084013567ffffffffffffffff80821115610dbe57600080fd5b9085019036601f830112610dd157600080fd5b813581811115610de357610de3610c9c565b610e13847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610d23565b91508082523684828501011115610e2957600080fd5b808484018584013760009082019093019290925250604082015292915050565b60005b83811015610e64578181015183820152602001610e4c565b50506000910152565b60008151808452610e85816020860160208601610e49565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015610f975782840389528151610160815186528682015187870152604080830151610f1a8289018273ffffffffffffffffffffffffffffffffffffffff169052565b5050606082810151908701526080808301519087015260a08083015163ffffffff169087015260c0808301519087015260e080830151908701526101008083015190870152610120808301519087015261014091820151918601819052610f8381870183610e6d565b9a87019a9550505090840190600101610ed5565b5091979650505050505050565b6000826060808201846000805b6003811015610f97578584038952825180518086526020918201918087019190600582901b88018101865b8381101561105a578982037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00185528551805173ffffffffffffffffffffffffffffffffffffffff16835283810151848401526040908101519083018c90526110478c840182610e6d565b9684019695840195925050600101610fdc565b509c81019c97509590950194505050600101610fb1565b6080808252855190820181905260009060209060a0840190828901845b828110156110c057815173ffffffffffffffffffffffffffffffffffffffff168452928401929084019060010161108e565b5050508381038285015286518082528783019183019060005b818110156110f5578351835292840192918401916001016110d9565b505084810360408601526111098188610eb7565b92505050828103606084015261111f8185610fa4565b979650505050505050565b60006020828403121561113c57600080fd5b815180151581146109aa57600080fd5b6020815260006109aa6020830184610e6d565b81810381811115611199577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600082516111b1818460208701610e49565b919091019291505056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index c0af4b113b..a430246f5f 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -529,6 +529,10 @@ fn main() { generate_contract("Signatures"); generate_contract("SimulateCode"); + // Support contract used for solver fee simulations. + generate_contract("AnyoneAuthenticator"); + generate_contract("Swapper"); + // Support contract used for global block stream. generate_contract("FetchBlock"); diff --git a/crates/contracts/solidity/AnyoneAuthenticator.sol b/crates/contracts/solidity/AnyoneAuthenticator.sol new file mode 100644 index 0000000000..7602ffb1a9 --- /dev/null +++ b/crates/contracts/solidity/AnyoneAuthenticator.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract AnyoneAuthenticator { + function isSolver(address) external pure returns (bool) { + return true; + } +} diff --git a/crates/contracts/solidity/Makefile b/crates/contracts/solidity/Makefile index 19ee35a44e..e7845b64de 100644 --- a/crates/contracts/solidity/Makefile +++ b/crates/contracts/solidity/Makefile @@ -8,12 +8,14 @@ TARGETDIR := ../../../target/solidity ARTIFACTDIR := ../artifacts CONTRACTS := \ + AnyoneAuthenticator.sol \ Balances.sol \ FetchBlock.sol \ Multicall.sol \ Signatures.sol \ SimulateCode.sol \ Solver.sol \ + Swapper.sol \ Trader.sol ARTIFACTS := $(patsubst %.sol,$(ARTIFACTDIR)/%.json,$(CONTRACTS)) diff --git a/crates/contracts/solidity/Swapper.sol b/crates/contracts/solidity/Swapper.sol new file mode 100644 index 0000000000..c9f224401c --- /dev/null +++ b/crates/contracts/solidity/Swapper.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { IERC20 } from "./interfaces/IERC20.sol"; +import { ISettlement, Interaction, Trade } from "./interfaces/ISettlement.sol"; +import { Caller } from "./libraries/Caller.sol"; +import { SafeERC20 } from "./libraries/SafeERC20.sol"; + +struct Asset { + address token; + uint256 amount; +} + +struct Allowance { + address spender; + uint256 amount; +} + +/// @title A contract for verifying DEX aggregator swaps for solving. +contract Swapper { + using Caller for *; + using SafeERC20 for *; + + /// @dev Simulates the execution of a single DEX swap over the CoW Protocol + /// settlement contract. This is used for accurately simulating gas costs + /// for orders with solver-computed fees. + /// + /// @param settlement - the CoW Protocol settlement contract. + /// @param sell - the asset being sold in the swap. + /// @param buy - the asset being bought in the swap. + /// @param allowance - the required ERC-20 allowance for the swap; the + /// approval will be me made on behalf of the settlement contract. + /// @param call - the call for executing the swap. + /// + /// @return gasUsed - the cumulative gas used for executing the simulated + /// settlement. + function swap( + ISettlement settlement, + Asset calldata sell, + Asset calldata buy, + Allowance calldata allowance, + Interaction calldata call + ) external returns ( + uint256 gasUsed + ) { + if (IERC20(sell.token).balanceOf(address(this)) < sell.amount) { + // The swapper does not have sufficient balance. This can happen + // when hooks set up required balance for a trade. This is currently + // not supported by this simulation, so return "0" to indicate that + // no simulation was possible and that heuristic gas estimates + // should be used instead. + return 0; + } + + // We first reset the allowance to 0 because some ERC20 tokens (e.g. USDT) + // require that due to this attack: + // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + // Before approving the amount we actually need. + IERC20(sell.token).safeApprove(address(settlement.vaultRelayer()), 0); + IERC20(sell.token).safeApprove(address(settlement.vaultRelayer()), sell.amount); + + address[] memory tokens = new address[](2); + tokens[0] = sell.token; + tokens[1] = buy.token; + + uint256[] memory clearingPrices = new uint256[](2); + clearingPrices[0] = buy.amount; + clearingPrices[1] = sell.amount; + + Trade[] memory trades = new Trade[](1); + trades[0] = Trade({ + sellTokenIndex: 0, + buyTokenIndex: 1, + receiver: address(0), + sellAmount: sell.amount, + buyAmount: buy.amount, + validTo: type(uint32).max, + appData: bytes32(0), + feeAmount: 0, + flags: 0x40, // EIP-1271 + // Actual amount is irrelevant because we configure a fill-or-kill + // order for which the settlement contract determines the + // `executedAmount` automatically. + executedAmount: 0, + signature: abi.encodePacked(address(this)) + }); + + Interaction[][3] memory interactions; + if ( + IERC20(sell.token).allowance(address(settlement), allowance.spender) + < allowance.amount + ) { + interactions[0] = new Interaction[](1); + interactions[0][0].target = sell.token; + interactions[0][0].callData = abi.encodeCall( + IERC20(sell.token).approve, + (allowance.spender, allowance.amount) + ); + } + interactions[1] = new Interaction[](1); + interactions[1][0] = call; + + gasUsed = address(settlement).doMeteredCallNoReturn( + abi.encodeCall( + settlement.settle, + (tokens, clearingPrices, trades, interactions) + ) + ); + } + + /// @dev Validate all signature requests. This makes "signing" CoW protocol + /// orders trivial. + function isValidSignature(bytes32, bytes calldata) external pure returns (bytes4) { + return 0x1626ba7e; + } +} + diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index c8d664d1fe..e4a7caec0e 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -70,12 +70,14 @@ include_contracts! { pub mod support { include_contracts! { + AnyoneAuthenticator; Balances; FetchBlock; Multicall; Signatures; SimulateCode; Solver; + Swapper; Trader; } } diff --git a/crates/solvers/config/example.balancer.toml b/crates/solvers/config/example.balancer.toml index 0d66a50dfb..f2f9f80742 100644 --- a/crates/solvers/config/example.balancer.toml +++ b/crates/solvers/config/example.balancer.toml @@ -1,5 +1,6 @@ +node-url = "http://localhost:8545" absolute-slippage = "40000000000000000" # Denominated in wei, optional -relative-slippage = "0.1" # Percentage in the [0, 1] range +relative-slippage = "0.001" # Percentage in the [0, 1] range risk-parameters = [0,0,0,0] [dex] diff --git a/crates/solvers/config/example.oneinch.toml b/crates/solvers/config/example.oneinch.toml index 41bda0395d..74dce21a9e 100644 --- a/crates/solvers/config/example.oneinch.toml +++ b/crates/solvers/config/example.oneinch.toml @@ -1,5 +1,7 @@ +node-url = "http://localhost:8545" absolute-slippage = "40000000000000000" # Denominated in wei, optional -relative-slippage = "0.1" # Percentage in the [0, 1] range +relative-slippage = "0.001" # Percentage in the [0, 1] range risk-parameters = [0,0,0,0] + [dex] chain-id = "1" diff --git a/crates/solvers/config/example.paraswap.toml b/crates/solvers/config/example.paraswap.toml index 43d66c134a..4e8c8b116d 100644 --- a/crates/solvers/config/example.paraswap.toml +++ b/crates/solvers/config/example.paraswap.toml @@ -1,6 +1,8 @@ +node-url = "http://localhost:8545" absolute-slippage = "40000000000000000" # Denominated in wei, optional -relative-slippage = "0.1" # Percentage in the [0, 1] range +relative-slippage = "0.001" # Percentage in the [0, 1] range risk-parameters = [0,0,0,0] + [dex] exclude-dexs = [] # which dexs to ignore as liquidity sources address = "0xdd2e786980CD58ACc5F64807b354c981f4094936" # public address of the solver diff --git a/crates/solvers/config/example.zeroex.toml b/crates/solvers/config/example.zeroex.toml index 8850f71b5e..6884a602ed 100644 --- a/crates/solvers/config/example.zeroex.toml +++ b/crates/solvers/config/example.zeroex.toml @@ -1,5 +1,6 @@ +node-url = "http://localhost:8545" absolute-slippage = "40000000000000000" # Denominated in wei, optional -relative-slippage = "0.1" # Percentage in the [0, 1] range +relative-slippage = "0.001" # Percentage in the [0, 1] range risk-parameters = [0,0,0,0] [dex] diff --git a/crates/solvers/src/domain/dex/mod.rs b/crates/solvers/src/domain/dex/mod.rs index de53869c2d..80ce2573a2 100644 --- a/crates/solvers/src/domain/dex/mod.rs +++ b/crates/solvers/src/domain/dex/mod.rs @@ -5,6 +5,7 @@ use { crate::{ domain::{auction, eth, order, solution}, + infra, util, }, ethereum_types::U256, @@ -101,13 +102,28 @@ impl Swap { /// Constructs a single order `solution::Solution` for this swap. Returns /// `None` if the swap is not valid for the specified order. - pub fn into_solution( + pub async fn into_solution( self, order: order::Order, gas_price: auction::GasPrice, sell_token: Option, score: solution::Score, + simulator: &infra::dex::Simulator, ) -> Option { + let gas = if order.class == order::Class::Limit { + match simulator.gas(order.owner(), &self).await { + Ok(value) => value, + Err(err) => { + tracing::warn!(?err, "gas simulation failed"); + return None; + } + } + } else { + // We are fine with just using heuristic gas for market orders, + // since it doesn't really play a role in the final solution. + self.gas + }; + let allowance = self.allowance(); let interactions = vec![solution::Interaction::Custom(solution::CustomInteraction { target: self.call.to.0, @@ -124,7 +140,7 @@ impl Swap { input: self.input, output: self.output, interactions, - gas: self.gas, + gas, } .into_solution(gas_price, sell_token, score) } diff --git a/crates/solvers/src/domain/order.rs b/crates/solvers/src/domain/order.rs index 17044f1fbd..2a686e3b98 100644 --- a/crates/solvers/src/domain/order.rs +++ b/crates/solvers/src/domain/order.rs @@ -19,6 +19,13 @@ pub struct Order { } impl Order { + /// Returns the order's owner address. + pub fn owner(&self) -> Address { + let mut bytes = [0_u8; 20]; + bytes.copy_from_slice(&self.uid.0[32..52]); + bytes.into() + } + /// Returns the order's fee amount as an asset. pub fn fee(&self) -> eth::Asset { eth::Asset { diff --git a/crates/solvers/src/domain/solver/dex/mod.rs b/crates/solvers/src/domain/solver/dex/mod.rs index eb3b455633..c2321fc97d 100644 --- a/crates/solvers/src/domain/solver/dex/mod.rs +++ b/crates/solvers/src/domain/solver/dex/mod.rs @@ -18,6 +18,9 @@ pub struct Dex { /// The DEX API client. dex: infra::dex::Dex, + /// A DEX swap gas simulator for computing limit order fees. + simulator: infra::dex::Simulator, + /// The slippage configuration to use for the solver. slippage: slippage::Limits, @@ -36,6 +39,11 @@ impl Dex { pub fn new(dex: infra::dex::Dex, config: infra::config::dex::Config) -> Self { Self { dex, + simulator: infra::dex::Simulator::new( + &config.node_url, + config.contracts.settlement, + config.contracts.authenticator, + ), slippage: config.slippage, concurrent_requests: config.concurrent_requests, fills: Fills::new(config.smallest_partial_fill), @@ -115,7 +123,10 @@ impl Dex { let sell = tokens.reference_price(&order.sell.token); let score = solution::Score::RiskAdjusted(self.risk.success_probability(swap.gas, gas_price, 1)); - let Some(solution) = swap.into_solution(order.clone(), gas_price, sell, score) else { + let Some(solution) = swap + .into_solution(order.clone(), gas_price, sell, score, &self.simulator) + .await + else { tracing::debug!("no solution for swap"); return None; }; diff --git a/crates/solvers/src/infra/blockchain.rs b/crates/solvers/src/infra/blockchain.rs new file mode 100644 index 0000000000..9bed81083b --- /dev/null +++ b/crates/solvers/src/infra/blockchain.rs @@ -0,0 +1,13 @@ +use std::time::Duration; + +/// Creates a node RPC instance. +pub fn rpc(url: &reqwest::Url) -> ethrpc::Web3 { + ethrpc::web3( + Default::default(), + reqwest::ClientBuilder::new() + .timeout(Duration::from_secs(10)) + .user_agent("cowprotocol-solver-engine/1.0.0"), + url, + "base", + ) +} diff --git a/crates/solvers/src/infra/config/dex/balancer/file.rs b/crates/solvers/src/infra/config/dex/balancer/file.rs index 48d6a26bcb..49a9f23b8a 100644 --- a/crates/solvers/src/infra/config/dex/balancer/file.rs +++ b/crates/solvers/src/infra/config/dex/balancer/file.rs @@ -20,10 +20,6 @@ struct Config { /// Optional Balancer V2 Vault contract address. If not specified, the /// default Vault contract address will be used. vault: Option, - - /// Optional CoW Protocol Settlement contract address. If not specified, - /// the default Settlement contract address will be used. - settlement: Option, } /// Load the driver configuration from a TOML file. @@ -44,10 +40,7 @@ pub async fn load(path: &Path) -> super::Config { .vault .map(eth::ContractAddress) .unwrap_or(contracts.balancer_vault), - settlement: config - .settlement - .map(eth::ContractAddress) - .unwrap_or(contracts.settlement), + settlement: base.contracts.settlement, }, base, } diff --git a/crates/solvers/src/infra/config/dex/file.rs b/crates/solvers/src/infra/config/dex/file.rs index e8f1b688a6..6b101e96b8 100644 --- a/crates/solvers/src/infra/config/dex/file.rs +++ b/crates/solvers/src/infra/config/dex/file.rs @@ -3,7 +3,7 @@ use { crate::{ domain::{dex::slippage, eth, Risk}, - infra::config::unwrap_or_log, + infra::{blockchain, config::unwrap_or_log, contracts}, util::serialize, }, bigdecimal::BigDecimal, @@ -17,6 +17,14 @@ use { #[derive(Debug, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] struct Config { + /// The node URL to use for simulations. + #[serde_as(as = "serde_with::DisplayFromStr")] + node_url: reqwest::Url, + + /// Optional CoW Protocol Settlement contract address. If not specified, + /// the default Settlement contract address will be used. + settlement: Option, + /// The relative slippage allowed by the solver. #[serde(default = "default_relative_slippage")] #[serde_as(as = "serde_with::DisplayFromStr")] @@ -71,7 +79,31 @@ pub async fn load(path: &Path) -> (super::Config, T) { let dex: T = unwrap_or_log(config.dex.try_into(), &path); + // Take advantage of the fact that deterministic deployment means that all + // CoW Protocol contracts have the same address. + let contracts = contracts::Contracts::for_chain(eth::ChainId::Mainnet); + let (settlement, authenticator) = if let Some(settlement) = config.settlement { + let authenticator = eth::ContractAddress({ + let web3 = blockchain::rpc(&config.node_url); + let settlement = ::contracts::GPv2Settlement::at(&web3, settlement); + settlement + .methods() + .authenticator() + .call() + .await + .unwrap_or_else(|e| panic!("error reading authenticator contract address: {e:?}")) + }); + (eth::ContractAddress(settlement), authenticator) + } else { + (contracts.settlement, contracts.authenticator) + }; + let config = super::Config { + node_url: config.node_url, + contracts: super::Contracts { + settlement, + authenticator, + }, slippage: slippage::Limits::new( config.relative_slippage, config.absolute_slippage.map(eth::Ether), diff --git a/crates/solvers/src/infra/config/dex/mod.rs b/crates/solvers/src/infra/config/dex/mod.rs index ae462c20c3..ac8cb2354d 100644 --- a/crates/solvers/src/infra/config/dex/mod.rs +++ b/crates/solvers/src/infra/config/dex/mod.rs @@ -9,7 +9,14 @@ use { std::num::NonZeroUsize, }; +pub struct Contracts { + pub settlement: eth::ContractAddress, + pub authenticator: eth::ContractAddress, +} + pub struct Config { + pub node_url: reqwest::Url, + pub contracts: Contracts, pub slippage: slippage::Limits, pub concurrent_requests: NonZeroUsize, pub smallest_partial_fill: eth::Ether, diff --git a/crates/solvers/src/infra/contracts.rs b/crates/solvers/src/infra/contracts.rs index 2633bd9da2..c2266143b9 100644 --- a/crates/solvers/src/infra/contracts.rs +++ b/crates/solvers/src/infra/contracts.rs @@ -4,6 +4,7 @@ use crate::domain::eth; pub struct Contracts { pub weth: eth::WethAddress, pub settlement: eth::ContractAddress, + pub authenticator: eth::ContractAddress, pub balancer_vault: eth::ContractAddress, } @@ -21,6 +22,7 @@ impl Contracts { Self { weth: eth::WethAddress(a(contracts::WETH9::raw_contract()).0), settlement: a(contracts::GPv2Settlement::raw_contract()), + authenticator: a(contracts::GPv2AllowListAuthentication::raw_contract()), balancer_vault: a(contracts::BalancerV2Vault::raw_contract()), } } diff --git a/crates/solvers/src/infra/dex/mod.rs b/crates/solvers/src/infra/dex/mod.rs index 5d073b5f9c..32dacaad4e 100644 --- a/crates/solvers/src/infra/dex/mod.rs +++ b/crates/solvers/src/infra/dex/mod.rs @@ -3,8 +3,11 @@ use crate::domain::{auction, dex}; pub mod balancer; pub mod oneinch; pub mod paraswap; +pub mod simulator; pub mod zeroex; +pub use self::simulator::Simulator; + /// A supported external DEX/DEX aggregator API. pub enum Dex { Balancer(balancer::Sor), diff --git a/crates/solvers/src/infra/dex/simulator.rs b/crates/solvers/src/infra/dex/simulator.rs new file mode 100644 index 0000000000..d17c2bfff0 --- /dev/null +++ b/crates/solvers/src/infra/dex/simulator.rs @@ -0,0 +1,128 @@ +use { + crate::{ + domain::{dex, eth}, + infra::blockchain, + }, + contracts::ethcontract::{self, web3}, + ethereum_types::{Address, U256}, + ethrpc::extensions::EthExt, + std::collections::HashMap, +}; + +/// A DEX swap simulator. +#[derive(Debug, Clone)] +pub struct Simulator { + web3: ethrpc::Web3, + settlement: eth::ContractAddress, + authenticator: eth::ContractAddress, +} + +impl Simulator { + /// Create a new simulator for computing DEX swap gas usage. + pub fn new( + url: &reqwest::Url, + settlement: eth::ContractAddress, + authenticator: eth::ContractAddress, + ) -> Self { + Self { + web3: blockchain::rpc(url), + settlement, + authenticator, + } + } + + /// Simulate the gas needed by a single order DEX swap. + /// + /// This will return a `None` if the gas simulation is unavailable. + pub async fn gas(&self, owner: Address, swap: &dex::Swap) -> Result { + let swapper = contracts::support::Swapper::at(&self.web3, owner); + let tx = swapper + .methods() + .swap( + self.settlement.0, + (swap.input.token.0, swap.input.amount), + (swap.output.token.0, swap.output.amount), + (swap.allowance.spender.0, swap.allowance.amount.get()), + ( + swap.call.to.0, + U256::zero(), + ethcontract::Bytes(swap.call.calldata.clone()), + ), + ) + .tx; + + let call = web3::types::CallRequest { + to: tx.to, + data: tx.data, + ..Default::default() + }; + + let code = |contract: &contracts::ethcontract::Contract| { + contract + .deployed_bytecode + .to_bytes() + .expect("contract bytecode is available") + }; + let overrides = HashMap::<_, _>::from_iter([ + // Setup up our trader code that actually executes the settlement + ( + swapper.address(), + ethrpc::extensions::StateOverride { + code: Some(code(contracts::support::Swapper::raw_contract())), + ..Default::default() + }, + ), + // Override the CoW protocol solver authenticator with one that + // allows any address to solve + ( + self.authenticator.0, + ethrpc::extensions::StateOverride { + code: Some(code(contracts::support::AnyoneAuthenticator::raw_contract())), + ..Default::default() + }, + ), + ]); + + let return_data = self + .web3 + .eth() + .call_with_state_overrides(call, web3::types::BlockNumber::Latest.into(), overrides) + .await? + .0; + + let gas = { + if return_data.len() != 32 { + return Err(Error::InvalidReturnData); + } + + U256::from_big_endian(&return_data) + }; + + // `gas == 0` means that the simulation is not possible. See + // `Swapper.sol` contract for more details. In this case, use the + // heuristic gas amount from the swap. + Ok(if gas.is_zero() { + tracing::info!( + gas = ?swap.gas, + "could not simulate dex swap to get gas used; fall back to gas estimate provided \ + by dex API" + ); + swap.gas + } else { + eth::Gas(gas) + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("error initializing simulator: {0}")] +pub struct InitializationError(#[from] ethcontract::errors::MethodError); + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("web3 error: {0:?}")] + Web3(#[from] web3::error::Error), + + #[error("invalid return data")] + InvalidReturnData, +} diff --git a/crates/solvers/src/infra/mod.rs b/crates/solvers/src/infra/mod.rs index 65261b1a83..e938aba499 100644 --- a/crates/solvers/src/infra/mod.rs +++ b/crates/solvers/src/infra/mod.rs @@ -1,3 +1,4 @@ +pub mod blockchain; pub mod cli; pub mod config; pub mod contracts; diff --git a/crates/solvers/src/tests/balancer/market_order.rs b/crates/solvers/src/tests/balancer/market_order.rs index 9d1d92d609..b9c96d6195 100644 --- a/crates/solvers/src/tests/balancer/market_order.rs +++ b/crates/solvers/src/tests/balancer/market_order.rs @@ -10,13 +10,13 @@ use { async fn sell() { let api = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::exact("sor"), - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "sell", "amount": "1000000000000000000", "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", @@ -167,13 +167,13 @@ async fn sell() { async fn buy() { let api = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::exact("sor"), - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "buy", "amount": "100000000000000000000", "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", diff --git a/crates/solvers/src/tests/balancer/mod.rs b/crates/solvers/src/tests/balancer/mod.rs index 543cab7c8b..4371c185d5 100644 --- a/crates/solvers/src/tests/balancer/mod.rs +++ b/crates/solvers/src/tests/balancer/mod.rs @@ -8,6 +8,7 @@ mod out_of_price; pub fn config(solver_addr: &SocketAddr) -> tests::Config { tests::Config::String(format!( r" +node-url = 'http://localhost:8545' risk-parameters = [0,0,0,0] [dex] endpoint = 'http://{solver_addr}/sor' diff --git a/crates/solvers/src/tests/balancer/not_found.rs b/crates/solvers/src/tests/balancer/not_found.rs index d3e2ae1788..03228a5adb 100644 --- a/crates/solvers/src/tests/balancer/not_found.rs +++ b/crates/solvers/src/tests/balancer/not_found.rs @@ -11,13 +11,13 @@ use { async fn test() { let api = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0x1111111111111111111111111111111111111111", "buyToken": "0x2222222222222222222222222222222222222222", "orderKind": "sell", "amount": "1000000000000000000", "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [], "swaps": [], diff --git a/crates/solvers/src/tests/balancer/out_of_price.rs b/crates/solvers/src/tests/balancer/out_of_price.rs index 4bc5f4c4ba..eae7be3fe8 100644 --- a/crates/solvers/src/tests/balancer/out_of_price.rs +++ b/crates/solvers/src/tests/balancer/out_of_price.rs @@ -13,13 +13,13 @@ use { async fn sell() { let api = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "sell", "amount": "1000000000000000000", "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", @@ -102,13 +102,13 @@ async fn sell() { async fn buy() { let api = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "buy", "amount": "100000000000000000000", "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", diff --git a/crates/solvers/src/tests/dex/partial_fill.rs b/crates/solvers/src/tests/dex/partial_fill.rs index 7ca6a6c303..c575926592 100644 --- a/crates/solvers/src/tests/dex/partial_fill.rs +++ b/crates/solvers/src/tests/dex/partial_fill.rs @@ -13,13 +13,13 @@ use { async fn tested_amounts_adjust_depending_on_response() { // observe::tracing::initialize_reentrant("solvers=trace"); let inner_request = |amount| { - json!({ + mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "sell", "amount": amount, "gasPrice": "15000000000", - }) + })) }; let no_swap_found_response = json!({ @@ -94,7 +94,30 @@ async fn tested_amounts_adjust_depending_on_response() { ]) .await; - let engine = tests::SolverEngine::new("balancer", balancer::config(&api.address)).await; + let simulation_node = mock::http::setup(vec![mock::http::Expectation::Post { + path: mock::http::Path::Any, + req: mock::http::RequestBody::Any, + res: { + json!({ + "id": 1, + "jsonrpc": "2.0", + "result": "0x0000000000000000000000000000000000000000000000000000000000015B3C" + }) + }, + }]) + .await; + + let config = tests::Config::String(format!( + r" +node-url = 'http://{}' +risk-parameters = [0,0,0,0] +[dex] +endpoint = 'http://{}/sor' + ", + simulation_node.address, api.address, + )); + + let engine = tests::SolverEngine::new("balancer", config).await; let auction = json!({ "id": "1", @@ -251,13 +274,13 @@ async fn tested_amounts_wrap_around() { .into_iter() .map(|amount| mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "buy", "amount": amount, "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [], "swaps": [], @@ -344,13 +367,13 @@ async fn moves_surplus_fee_to_buy_token() { let api = mock::http::setup(vec![ mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "sell", "amount": "2000000000000000000", "gasPrice": "6000000000000", - }), + })), res: json!({ "tokenAddresses": [], "swaps": [], @@ -366,13 +389,13 @@ async fn moves_surplus_fee_to_buy_token() { }, mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "sell", "amount": "1000000000000000000", "gasPrice": "6000000000000", - }), + })), res: json!({ "tokenAddresses": [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", @@ -402,7 +425,35 @@ async fn moves_surplus_fee_to_buy_token() { ]) .await; - let engine = tests::SolverEngine::new("balancer", balancer::config(&api.address)).await; + let simulation_node = mock::http::setup(vec![mock::http::Expectation::Post { + path: mock::http::Path::Any, + req: mock::http::RequestBody::Any, + res: { + json!({ + "id": 1, + "jsonrpc": "2.0", + // If the simulation logic returns 0 it means that the user did not have the + // required balance. This could be caused by a pre-interaction that acquires the + // necessary sell_token before the trade which is currently not supported by the + // simulation loic. + // In that case we fall back to the heuristic gas price we had in the past. + "result": "0x0000000000000000000000000000000000000000000000000000000000000000" + }) + }, + }]) + .await; + + let config = tests::Config::String(format!( + r" +node-url = 'http://{}' +risk-parameters = [0,0,0,0] +[dex] +endpoint = 'http://{}/sor' + ", + simulation_node.address, api.address, + )); + + let engine = tests::SolverEngine::new("balancer", config).await; let auction = json!({ "id": "1", @@ -542,13 +593,13 @@ async fn moves_surplus_fee_to_buy_token() { async fn insufficient_room_for_surplus_fee() { let api = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "sell", "amount": "1000000000000000000", "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", @@ -643,13 +694,13 @@ async fn insufficient_room_for_surplus_fee() { async fn market() { let api = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": "sell", "amount": "1000000000000000000", "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", diff --git a/crates/solvers/src/tests/dex/wrong_execution.rs b/crates/solvers/src/tests/dex/wrong_execution.rs index 164783cfdf..91a479e67b 100644 --- a/crates/solvers/src/tests/dex/wrong_execution.rs +++ b/crates/solvers/src/tests/dex/wrong_execution.rs @@ -42,7 +42,7 @@ async fn test() { ] { let api = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "sellToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "buyToken": "0xba100000625a3754423978a60c9317c58a424e3d", "orderKind": side, @@ -52,7 +52,7 @@ async fn test() { "227598784442065388110" }, "gasPrice": "15000000000", - }), + })), res: json!({ "tokenAddresses": [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", diff --git a/crates/solvers/src/tests/legacy/attaching_approvals.rs b/crates/solvers/src/tests/legacy/attaching_approvals.rs index 31b9f36e26..f157705459 100644 --- a/crates/solvers/src/tests/legacy/attaching_approvals.rs +++ b/crates/solvers/src/tests/legacy/attaching_approvals.rs @@ -10,7 +10,7 @@ use { async fn test() { let legacy_solver = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "amms": {}, "metadata": { "auction_id": 1234, @@ -38,7 +38,7 @@ async fn test() { "normalize_priority": 0, } } - }), + })), res: json!({ "orders": {}, "prices": {}, diff --git a/crates/solvers/src/tests/legacy/concentrated_liquidity.rs b/crates/solvers/src/tests/legacy/concentrated_liquidity.rs index 84d3202f20..1221312907 100644 --- a/crates/solvers/src/tests/legacy/concentrated_liquidity.rs +++ b/crates/solvers/src/tests/legacy/concentrated_liquidity.rs @@ -10,7 +10,7 @@ use { async fn test() { let legacy_solver = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "amms": { "0x97b744df0b59d93a866304f97431d8efad29a08d": { "address": "0x97b744df0b59d93a866304f97431d8efad29a08d", @@ -65,7 +65,7 @@ async fn test() { "external_price": null, } } - }), + })), res: json!({ "orders": {}, "prices": {}, diff --git a/crates/solvers/src/tests/legacy/jit_order.rs b/crates/solvers/src/tests/legacy/jit_order.rs index 60955c5f25..18a1c8a4b5 100644 --- a/crates/solvers/src/tests/legacy/jit_order.rs +++ b/crates/solvers/src/tests/legacy/jit_order.rs @@ -10,7 +10,7 @@ use { async fn test() { let legacy_solver = mock::http::setup(vec![mock::http::Expectation::Post { path: mock::http::Path::Any, - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "amms": {}, "metadata": { "auction_id": 1, @@ -30,7 +30,7 @@ async fn test() { "external_price": null, } } - }), + })), res: json!({ "orders": {}, "prices": {}, diff --git a/crates/solvers/src/tests/legacy/market_order.rs b/crates/solvers/src/tests/legacy/market_order.rs index fca0a1c693..68988df844 100644 --- a/crates/solvers/src/tests/legacy/market_order.rs +++ b/crates/solvers/src/tests/legacy/market_order.rs @@ -19,7 +19,7 @@ async fn quote() { &auction_id=1\ &request_id=0" ), - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "amms": { "0x97b744df0b59d93a866304f97431d8efad29a08d": { "address": "0x97b744df0b59d93a866304f97431d8efad29a08d", @@ -85,7 +85,7 @@ async fn quote() { "normalize_priority": 0 } } - }), + })), res: json!({ "orders": { "0": { @@ -223,7 +223,7 @@ async fn solve() { &auction_id=1234\ &request_id=0", ), - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "amms": { "0x97b744df0b59d93a866304f97431d8efad29a08d": { "address": "0x97b744df0b59d93a866304f97431d8efad29a08d", @@ -289,7 +289,7 @@ async fn solve() { "normalize_priority": 0 } } - }), + })), res: json!({ "orders": { "0": { diff --git a/crates/solvers/src/tests/mock/http.rs b/crates/solvers/src/tests/mock/http.rs index e5bdcd7788..f91f59d797 100644 --- a/crates/solvers/src/tests/mock/http.rs +++ b/crates/solvers/src/tests/mock/http.rs @@ -71,11 +71,19 @@ pub enum Expectation { }, Post { path: Path, - req: serde_json::Value, + req: RequestBody, res: serde_json::Value, }, } +#[derive(Clone, Debug)] +pub enum RequestBody { + /// The received `[RequestBody]` has to match the provided value exactly. + Exact(serde_json::Value), + /// Any `[RequestBody]` will be accepted. + Any, +} + /// Drop handle that will verify that the server task didn't panic throughout /// the test and that all the expectations have been met. pub struct ServerHandle { @@ -105,7 +113,11 @@ impl Drop for ServerHandle { !self.handle.is_finished(), "mock http server terminated before test ended" ); - assert_eq!(self.expectations.lock().unwrap().len(), 0); + assert_eq!( + self.expectations.lock().unwrap().len(), + 0, + "mock server did not receive enough requests" + ); self.handle.abort(); } } @@ -226,7 +238,10 @@ fn post( let full_path = full_path(path, query); assert_eq!(full_path, expected_path, "POST request has unexpected path"); - assert_eq!(req, expected_req, "POST request has unexpected body"); + match expected_req { + RequestBody::Exact(value) => assert_eq!(req, value, "POST request has unexpected body"), + RequestBody::Any => (), + } res }; diff --git a/crates/solvers/src/tests/oneinch/mod.rs b/crates/solvers/src/tests/oneinch/mod.rs index aadc863724..72257dc172 100644 --- a/crates/solvers/src/tests/oneinch/mod.rs +++ b/crates/solvers/src/tests/oneinch/mod.rs @@ -8,6 +8,7 @@ mod out_of_price; pub fn config(solver_addr: &SocketAddr) -> tests::Config { tests::Config::String(format!( r" +node-url = 'http://localhost:8545' risk-parameters = [0,0,0,0] [dex] chain-id = '1' diff --git a/crates/solvers/src/tests/paraswap/market_order.rs b/crates/solvers/src/tests/paraswap/market_order.rs index 96fbcf02f5..01922b7d39 100644 --- a/crates/solvers/src/tests/paraswap/market_order.rs +++ b/crates/solvers/src/tests/paraswap/market_order.rs @@ -72,7 +72,7 @@ async fn sell() { }, mock::http::Expectation::Post { path: mock::http::Path::exact("transactions/1?ignoreChecks=true"), - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "destToken": "0xe41d2489571d322189246dafa5ebde1f4699f498", "srcAmount": "1000000000000000000", @@ -135,7 +135,7 @@ async fn sell() { }, "userAddress": "0xe0b3700e0aadcb18ed8d4bff648bc99896a18ad1", "partner": "cow" - }), + })), res: json!({ "from": "0xe0b3700e0aadcb18ed8d4bff648bc99896a18ad1", "to": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", @@ -318,7 +318,7 @@ async fn buy() { }, mock::http::Expectation::Post { path: mock::http::Path::exact("transactions/1?ignoreChecks=true"), - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "destToken": "0xe41d2489571d322189246dafa5ebde1f4699f498", "srcAmount": "124940475326949378", @@ -389,7 +389,7 @@ async fn buy() { }, "userAddress": "0xe0b3700e0aadcb18ed8d4bff648bc99896a18ad1", "partner": "cow" - }), + })), res: json!({ "from": "0xe0b3700e0aadcb18ed8d4bff648bc99896a18ad1", "to": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", diff --git a/crates/solvers/src/tests/paraswap/mod.rs b/crates/solvers/src/tests/paraswap/mod.rs index f3152acdd5..4edaf265bc 100644 --- a/crates/solvers/src/tests/paraswap/mod.rs +++ b/crates/solvers/src/tests/paraswap/mod.rs @@ -8,6 +8,7 @@ mod out_of_price; pub fn config(solver_addr: &SocketAddr) -> tests::Config { tests::Config::String(format!( r" +node-url = 'http://localhost:8545' risk-parameters = [0,0,0,0] [dex] endpoint = 'http://{solver_addr}' diff --git a/crates/solvers/src/tests/paraswap/out_of_price.rs b/crates/solvers/src/tests/paraswap/out_of_price.rs index fe78fdb580..ab25bad137 100644 --- a/crates/solvers/src/tests/paraswap/out_of_price.rs +++ b/crates/solvers/src/tests/paraswap/out_of_price.rs @@ -75,7 +75,7 @@ async fn sell() { }, mock::http::Expectation::Post { path: mock::http::Path::exact("transactions/1?ignoreChecks=true"), - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "destToken": "0xe41d2489571d322189246dafa5ebde1f4699f498", "srcAmount": "1000000000000000000", @@ -138,7 +138,7 @@ async fn sell() { }, "userAddress": "0xe0b3700e0aadcb18ed8d4bff648bc99896a18ad1", "partner": "cow" - }), + })), res: json!({ "from": "0xe0b3700e0aadcb18ed8d4bff648bc99896a18ad1", "to": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", @@ -276,7 +276,7 @@ async fn buy() { }, mock::http::Expectation::Post { path: mock::http::Path::exact("transactions/1?ignoreChecks=true"), - req: json!({ + req: mock::http::RequestBody::Exact(json!({ "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "destToken": "0xe41d2489571d322189246dafa5ebde1f4699f498", "srcAmount": "124940475326949378", @@ -347,7 +347,7 @@ async fn buy() { }, "userAddress": "0xe0b3700e0aadcb18ed8d4bff648bc99896a18ad1", "partner": "cow" - }), + })), res: json!({ "from": "0xe0b3700e0aadcb18ed8d4bff648bc99896a18ad1", "to": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", diff --git a/crates/solvers/src/tests/zeroex/mod.rs b/crates/solvers/src/tests/zeroex/mod.rs index 342a2f1b66..34dc8a74a7 100644 --- a/crates/solvers/src/tests/zeroex/mod.rs +++ b/crates/solvers/src/tests/zeroex/mod.rs @@ -9,6 +9,7 @@ mod out_of_price; pub fn config(solver_addr: &SocketAddr) -> tests::Config { tests::Config::String(format!( r" +node-url = 'http://localhost:8545' risk-parameters = [0,0,0,0] [dex] endpoint = 'http://{solver_addr}/swap/v1/' diff --git a/crates/solvers/src/tests/zeroex/options.rs b/crates/solvers/src/tests/zeroex/options.rs index fb04c76682..c0b2a009d8 100644 --- a/crates/solvers/src/tests/zeroex/options.rs +++ b/crates/solvers/src/tests/zeroex/options.rs @@ -181,7 +181,8 @@ async fn test() { .await; let config = tests::Config::String(format!( - " + r" +node-url = 'http://localhost:8545' relative-slippage = '0.1' risk-parameters = [0,0,0,0] [dex]