From eb98aaf571f26d66897b5cd51d0a93efc5d07cc0 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 2 Dec 2024 13:18:39 +0000 Subject: [PATCH 01/25] Support JIT orders in the trade verifier (#3085) # Description Closes task n2 from https://github.com/cowprotocol/services/issues/3082 by implementing support of the quotes with JIT orders in the trade verifier. For a gradual migration, it has to support both quote versions. # Changes - [ ] Added a new version of the trade with JIT orders. - [ ] Utilized the clearing prices to calculate the out amount. - [ ] Altered the `Solver.sol` helper contract to fetch all token balances as was proposed in [one of the comments](https://github.com/cowprotocol/services/pull/3085#discussion_r1830692576), which reduces the overall code complexity. - [ ] Bumped into an issue while converting floats into `BigRational`. ~~Implemented a workaround with converting float's string representation into `BigRational`.~~ Used `BigDecimal` in the config instead. ## How to test Unit tests. e2e would be possible only once the driver support is implemented(see https://github.com/cowprotocol/services/pull/3103). --- Cargo.lock | 1 + crates/autopilot/src/run.rs | 1 + crates/contracts/artifacts/Solver.json | 2 +- crates/contracts/solidity/Solver.sol | 41 +- crates/e2e/Cargo.toml | 1 + crates/e2e/tests/e2e/quote_verification.rs | 13 +- crates/orderbook/src/run.rs | 1 + crates/shared/src/price_estimation/factory.rs | 31 +- crates/shared/src/price_estimation/mod.rs | 3 +- .../src/price_estimation/trade_verifier.rs | 455 ++++++++++++++---- crates/shared/src/trade_finding/external.rs | 94 +++- crates/shared/src/trade_finding/mod.rs | 138 +++++- 12 files changed, 617 insertions(+), 164 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4d50d7d64..17ca562be0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1856,6 +1856,7 @@ dependencies = [ "app-data", "autopilot", "axum", + "bigdecimal", "chrono", "clap", "contracts", diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 4a5f0a6503..59abed8253 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -332,6 +332,7 @@ pub async fn run(args: Arguments) { code_fetcher: code_fetcher.clone(), }, ) + .await .expect("failed to initialize price estimator factory"); let native_price_estimator = price_estimator_factory diff --git a/crates/contracts/artifacts/Solver.json b/crates/contracts/artifacts/Solver.json index 8248b515c2..f4de20dccc 100644 --- a/crates/contracts/artifacts/Solver.json +++ b/crates/contracts/artifacts/Solver.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"buyToken","type":"address"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610992806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b5780639efde0c214610050575b600080fd5b61004e61004936600461074f565b61007a565b005b61006361005e366004610796565b6101af565b604051610071929190610894565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014291906108e2565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a610187908261092a565b6101939061116c610943565b6000808282546101a39190610943565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d811660048301528b81166024830152604482018b905288811660648301528c16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50506040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8c811660048301528e16602482015260006044820152309150633bbb2e1d90606401600060405180830381600087803b1580156103b957600080fd5b505af11580156103cd573d6000803e3d6000fd5b50506040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8b811660048301528f16602482015260006044820152309250633bbb2e1d9150606401600060405180830381600087803b15801561044757600080fd5b505af115801561045b573d6000803e3d6000fd5b5050505060005a90506104d186868080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e73ffffffffffffffffffffffffffffffffffffffff1661067a90919063ffffffff16565b506000545a6104e0908361092a565b6104ea919061092a565b6040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d811660048301528f166024820152600060448201529093503090633bbb2e1d90606401600060405180830381600087803b15801561056357600080fd5b505af1158015610577573d6000803e3d6000fd5b505050503073ffffffffffffffffffffffffffffffffffffffff16633bbb2e1d8a8f60006040518463ffffffff1660e01b81526004016105e59392919073ffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152901515604082015260600190565b600060405180830381600087803b1580156105ff57600080fd5b505af1158015610613573d6000803e3d6000fd5b50505050600180548060200260200160405190810160405280929190818152602001828054801561066357602002820191906000526020600020905b81548152602001906001019080831161064f575b50505050509150509a509a98505050505050505050565b60606106888360008461068f565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516106b99190610956565b60006040518083038185875af1925050503d80600081146106f6576040519150601f19603f3d011682016040523d82523d6000602084013e6106fb565b606091505b50925090508061070d57815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461073757600080fd5b50565b8035801515811461074a57600080fd5b919050565b60008060006060848603121561076457600080fd5b833561076f81610715565b9250602084013561077f81610715565b915061078d6040850161073a565b90509250925092565b6000806000806000806000806000806101208b8d0312156107b657600080fd5b8a356107c181610715565b995060208b01356107d181610715565b985060408b01356107e181610715565b975060608b0135965060808b01356107f881610715565b955060a08b013561080881610715565b945060c08b013561081881610715565b935060e08b013567ffffffffffffffff8082111561083557600080fd5b818d0191508d601f83011261084957600080fd5b81358181111561085857600080fd5b8e602082850101111561086a57600080fd5b6020830195508094505050506108836101008c0161073a565b90509295989b9194979a5092959850565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156108d5578451835293830193918301916001016108b9565b5090979650505050505050565b6000602082840312156108f457600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561093d5761093d6108fb565b92915050565b8082018082111561093d5761093d6108fb565b6000825160005b81811015610977576020818601810151858301520161095d565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b5780639efde0c214610050575b600080fd5b61004e61004936600461074f565b61007a565b005b61006361005e366004610796565b6101af565b604051610071929190610894565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014291906108e2565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a610187908261092a565b6101939061116c610943565b6000808282546101a39190610943565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d811660048301528b81166024830152604482018b905288811660648301528c16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50506040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8c811660048301528e16602482015260006044820152309150633bbb2e1d90606401600060405180830381600087803b1580156103b957600080fd5b505af11580156103cd573d6000803e3d6000fd5b50506040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8b811660048301528f16602482015260006044820152309250633bbb2e1d9150606401600060405180830381600087803b15801561044757600080fd5b505af115801561045b573d6000803e3d6000fd5b5050505060005a90506104d186868080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e73ffffffffffffffffffffffffffffffffffffffff1661067a90919063ffffffff16565b506000545a6104e0908361092a565b6104ea919061092a565b6040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d811660048301528f166024820152600060448201529093503090633bbb2e1d90606401600060405180830381600087803b15801561056357600080fd5b505af1158015610577573d6000803e3d6000fd5b505050503073ffffffffffffffffffffffffffffffffffffffff16633bbb2e1d8a8f60006040518463ffffffff1660e01b81526004016105e59392919073ffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152901515604082015260600190565b600060405180830381600087803b1580156105ff57600080fd5b505af1158015610613573d6000803e3d6000fd5b50505050600180548060200260200160405190810160405280929190818152602001828054801561066357602002820191906000526020600020905b81548152602001906001019080831161064f575b50505050509150509a509a98505050505050505050565b60606106888360008461068f565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516106b99190610956565b60006040518083038185875af1925050503d80600081146106f6576040519150601f19603f3d011682016040523d82523d6000602084013e6106fb565b606091505b50925090508061070d57815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461073757600080fd5b50565b8035801515811461074a57600080fd5b919050565b60008060006060848603121561076457600080fd5b833561076f81610715565b9250602084013561077f81610715565b915061078d6040850161073a565b90509250925092565b6000806000806000806000806000806101208b8d0312156107b657600080fd5b8a356107c181610715565b995060208b01356107d181610715565b985060408b01356107e181610715565b975060608b0135965060808b01356107f881610715565b955060a08b013561080881610715565b945060c08b013561081881610715565b935060e08b013567ffffffffffffffff8082111561083557600080fd5b818d0191508d601f83011261084957600080fd5b81358181111561085857600080fd5b8e602082850101111561086a57600080fd5b6020830195508094505050506108836101008c0161073a565b90509295989b9194979a5092959850565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156108d5578451835293830193918301916001016108b9565b5090979650505050505050565b6000602082840312156108f457600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561093d5761093d6108fb565b92915050565b8082018082111561093d5761093d6108fb565b6000825160005b81811015610977576020818601810151858301520161095d565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610941806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063cc6b67a914610050575b600080fd5b61004e610049366004610600565b61007a565b005b61006361005e3660046106d5565b6101af565b6040516100719291906107bf565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610142919061080d565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a6101879082610855565b6101939061116c61086e565b6000808282546101a3919061086e565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50505061034e88888f6103c9565b6103598d86866104a1565b915061036688888f6103c9565b60018054806020026020016040519081016040528092919081815260200182805480156103b257602002820191906000526020600020905b81548152602001906001019080831161039e575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d8585848181106103ec576103ec610881565b905060200201602081019061040191906108b0565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561047657600080fd5b505af115801561048a573d6000803e3d6000fd5b505050508080610499906108cd565b9150506103cc565b6000805a90506104fe84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610520565b506000545a61050d9083610855565b6105179190610855565b95945050505050565b606061052e83600084610535565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161055f9190610905565b60006040518083038185875af1925050503d806000811461059c576040519150601f19603f3d011682016040523d82523d6000602084013e6105a1565b606091505b5092509050806105b357815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105dd57600080fd5b50565b80356105eb816105bb565b919050565b803580151581146105eb57600080fd5b60008060006060848603121561061557600080fd5b8335610620816105bb565b92506020840135610630816105bb565b915061063e604085016105f0565b90509250925092565b60008083601f84011261065957600080fd5b50813567ffffffffffffffff81111561067157600080fd5b6020830191508360208260051b850101111561068c57600080fd5b9250929050565b60008083601f8401126106a557600080fd5b50813567ffffffffffffffff8111156106bd57600080fd5b60208301915083602082850101111561068c57600080fd5b60008060008060008060008060008060006101208c8e0312156106f757600080fd5b6107018c356105bb565b8b359a5061071260208d01356105bb565b60208c0135995061072560408d016105e0565b985060608c0135975061073a60808d016105e0565b965067ffffffffffffffff8060a08e0135111561075657600080fd5b6107668e60a08f01358f01610647565b909750955061077760c08e016105e0565b94508060e08e0135111561078a57600080fd5b5061079b8d60e08e01358e01610693565b90935091506107ad6101008d016105f0565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015610800578451835293830193918301916001016107e4565b5090979650505050505050565b60006020828403121561081f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561086857610868610826565b92915050565b8082018082111561086857610868610826565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108c257600080fd5b813561052e816105bb565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108fe576108fe610826565b5060010190565b6000825160005b81811015610926576020818601810151858301520161090c565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063cc6b67a914610050575b600080fd5b61004e610049366004610600565b61007a565b005b61006361005e3660046106d5565b6101af565b6040516100719291906107bf565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610142919061080d565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a6101879082610855565b6101939061116c61086e565b6000808282546101a3919061086e565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50505061034e88888f6103c9565b6103598d86866104a1565b915061036688888f6103c9565b60018054806020026020016040519081016040528092919081815260200182805480156103b257602002820191906000526020600020905b81548152602001906001019080831161039e575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d8585848181106103ec576103ec610881565b905060200201602081019061040191906108b0565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561047657600080fd5b505af115801561048a573d6000803e3d6000fd5b505050508080610499906108cd565b9150506103cc565b6000805a90506104fe84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610520565b506000545a61050d9083610855565b6105179190610855565b95945050505050565b606061052e83600084610535565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161055f9190610905565b60006040518083038185875af1925050503d806000811461059c576040519150601f19603f3d011682016040523d82523d6000602084013e6105a1565b606091505b5092509050806105b357815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105dd57600080fd5b50565b80356105eb816105bb565b919050565b803580151581146105eb57600080fd5b60008060006060848603121561061557600080fd5b8335610620816105bb565b92506020840135610630816105bb565b915061063e604085016105f0565b90509250925092565b60008083601f84011261065957600080fd5b50813567ffffffffffffffff81111561067157600080fd5b6020830191508360208260051b850101111561068c57600080fd5b9250929050565b60008083601f8401126106a557600080fd5b50813567ffffffffffffffff8111156106bd57600080fd5b60208301915083602082850101111561068c57600080fd5b60008060008060008060008060008060006101208c8e0312156106f757600080fd5b6107018c356105bb565b8b359a5061071260208d01356105bb565b60208c0135995061072560408d016105e0565b985060608c0135975061073a60808d016105e0565b965067ffffffffffffffff8060a08e0135111561075657600080fd5b6107668e60a08f01358f01610647565b909750955061077760c08e016105e0565b94508060e08e0135111561078a57600080fd5b5061079b8d60e08e01358e01610693565b90935091506107ad6101008d016105f0565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015610800578451835293830193918301916001016107e4565b5090979650505050505050565b60006020828403121561081f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561086857610868610826565b92915050565b8082018082111561086857610868610826565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108c257600080fd5b813561052e816105bb565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108fe576108fe610826565b5060010190565b6000825160005b81811015610926576020818601810151858301520161090c565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/solidity/Solver.sol b/crates/contracts/solidity/Solver.sol index 8e9236b20a..9833c1e5c1 100644 --- a/crates/contracts/solidity/Solver.sol +++ b/crates/contracts/solidity/Solver.sol @@ -31,8 +31,8 @@ contract Solver { /// @param trader - address of the order owner doing the trade /// @param sellToken - address of the token being sold /// @param sellAmount - amount being sold - /// @param buyToken - address of the token being bought /// @param nativeToken - ERC20 version of the chain's token + /// @param tokens - list of tokens used in the trade /// @param receiver - address receiving the bought tokens /// @param settlementCall - the calldata of the `settle()` call /// @param mockPreconditions - controls whether things like ETH wrapping @@ -47,8 +47,8 @@ contract Solver { address payable trader, address sellToken, uint256 sellAmount, - address buyToken, address nativeToken, + address[] calldata tokens, address payable receiver, bytes calldata settlementCall, bool mockPreconditions @@ -76,13 +76,14 @@ contract Solver { // contract. receiver.call{value: 0}(""); - this.storeBalance(sellToken, address(settlementContract), false); - this.storeBalance(buyToken, address(settlementContract), false); - uint256 gasStart = gasleft(); - address(settlementContract).doCall(settlementCall); - gasUsed = gasStart - gasleft() - _simulationOverhead; - this.storeBalance(sellToken, address(settlementContract), false); - this.storeBalance(buyToken, address(settlementContract), false); + // Store pre-settlement balances + _storeSettlementBalances(tokens, settlementContract); + + gasUsed = _executeSettlement(address(settlementContract), settlementCall); + + // Store post-settlement balances + _storeSettlementBalances(tokens, settlementContract); + queriedBalances = _queriedBalances; } @@ -104,4 +105,26 @@ contract Solver { _simulationOverhead += gasStart - gasleft() + 4460; } } + + /// @dev Helper function that reads and stores the balances of the `settlementContract` for each token in `tokens`. + /// @param tokens - list of tokens used in the trade + /// @param settlementContract - the settlement contract whose balances are being read + function _storeSettlementBalances(address[] calldata tokens, ISettlement settlementContract) internal { + for (uint256 i = 0; i < tokens.length; i++) { + this.storeBalance(tokens[i], address(settlementContract), false); + } + } + + /// @dev Executes the settlement and measures the gas used. + /// @param settlementContract The address of the settlement contract. + /// @param settlementCall The calldata for the settlement function. + /// @return gasUsed The amount of gas used during the settlement execution. + function _executeSettlement( + address settlementContract, + bytes calldata settlementCall + ) private returns (uint256 gasUsed) { + uint256 gasStart = gasleft(); + address(settlementContract).doCall(settlementCall); + gasUsed = gasStart - gasleft() - _simulationOverhead; + } } diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 96a90421ca..659750454d 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -10,6 +10,7 @@ app-data = { path = "../app-data" } anyhow = { workspace = true } autopilot = { path = "../autopilot" } axum = { workspace = true } +bigdecimal = { workspace = true } chrono = { workspace = true } clap = { workspace = true } contracts = { path = "../contracts" } diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index cc9cf2c132..5e442d2e35 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -1,4 +1,5 @@ use { + bigdecimal::{BigDecimal, Zero}, e2e::setup::*, ethcontract::H160, ethrpc::Web3, @@ -13,7 +14,7 @@ use { Estimate, Verification, }, - trade_finding::{Interaction, Trade}, + trade_finding::{Interaction, LegacyTrade, TradeKind}, }, std::{str::FromStr, sync::Arc}, }; @@ -61,8 +62,10 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { block_stream, onchain.contracts().gp_settlement.address(), onchain.contracts().weth.address(), - 0.0, - ); + BigDecimal::zero(), + ) + .await + .unwrap(); let verify_trade = |tx_origin| { let verifier = verifier.clone(); @@ -86,7 +89,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { sell_token_source: SellTokenSource::Erc20, buy_token_destination: BuyTokenDestination::Erc20, }, - Trade { + TradeKind::Legacy(LegacyTrade { out_amount: 16380122291179526144u128.into(), gas_estimate: Some(225000), interactions: vec![Interaction { @@ -98,7 +101,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { solver: H160::from_str("0xe3067c7c27c1038de4e8ad95a83b927d23dfbd99") .unwrap(), tx_origin, - }, + }), ) .await } diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index c3a022ef31..16c27567b0 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -273,6 +273,7 @@ pub async fn run(args: Arguments) { code_fetcher: code_fetcher.clone(), }, ) + .await .expect("failed to initialize price estimator factory"); let native_price_estimator = price_estimator_factory diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 6019356cf9..03b2083264 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -73,14 +73,14 @@ pub struct Components { } impl<'a> PriceEstimatorFactory<'a> { - pub fn new( + pub async fn new( args: &'a Arguments, shared_args: &'a arguments::Arguments, network: Network, components: Components, ) -> Result { Ok(Self { - trade_verifier: Self::trade_verifier(args, shared_args, &network, &components), + trade_verifier: Self::trade_verifier(args, shared_args, &network, &components).await?, args, network, components, @@ -88,13 +88,15 @@ impl<'a> PriceEstimatorFactory<'a> { }) } - fn trade_verifier( + async fn trade_verifier( args: &'a Arguments, shared_args: &arguments::Arguments, network: &Network, components: &Components, - ) -> Option> { - let web3 = network.simulation_web3.clone()?; + ) -> Result>> { + let Some(web3) = network.simulation_web3.clone() else { + return Ok(None); + }; let web3 = ethrpc::instrumented::instrument_with_label(&web3, "simulator".into()); let tenderly = shared_args @@ -111,14 +113,17 @@ impl<'a> PriceEstimatorFactory<'a> { None => Arc::new(web3.clone()), }; - Some(Arc::new(TradeVerifier::new( - web3, - simulator, - components.code_fetcher.clone(), - network.block_stream.clone(), - network.settlement, - network.native_token, - args.quote_inaccuracy_limit, + Ok(Some(Arc::new( + TradeVerifier::new( + web3, + simulator, + components.code_fetcher.clone(), + network.block_stream.clone(), + network.settlement, + network.native_token, + args.quote_inaccuracy_limit.clone(), + ) + .await?, ))) } diff --git a/crates/shared/src/price_estimation/mod.rs b/crates/shared/src/price_estimation/mod.rs index 6a8fa88674..8355acef1c 100644 --- a/crates/shared/src/price_estimation/mod.rs +++ b/crates/shared/src/price_estimation/mod.rs @@ -5,6 +5,7 @@ use { trade_finding::Interaction, }, anyhow::Result, + bigdecimal::BigDecimal, ethcontract::{H160, U256}, futures::future::BoxFuture, itertools::Itertools, @@ -192,7 +193,7 @@ pub struct Arguments { /// E.g. a value of `0.01` means at most 1 percent of the sell or buy tokens /// can be paid out of the settlement contract buffers. #[clap(long, env, default_value = "1.")] - pub quote_inaccuracy_limit: f64, + pub quote_inaccuracy_limit: BigDecimal, /// How strict quote verification should be. #[clap( diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 258e928ef3..02e20a24d9 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -3,11 +3,16 @@ use { crate::{ code_fetching::CodeFetching, code_simulation::CodeSimulating, - encoded_settlement::{encode_trade, EncodedSettlement}, + encoded_settlement::{encode_trade, EncodedSettlement, EncodedTrade}, interaction::EncodedInteraction, - trade_finding::{Interaction, Trade}, + trade_finding::{ + external::{dto, dto::JitOrder}, + Interaction, + TradeKind, + }, }, anyhow::{Context, Result}, + bigdecimal::BigDecimal, contracts::{ deployed_bytecode, dummy_contract, @@ -21,21 +26,26 @@ use { model::{ order::{OrderData, OrderKind, BUY_ETH_ADDRESS}, signature::{Signature, SigningScheme}, + DomainSeparator, }, num::BigRational, - number::{conversions::u256_to_big_rational, nonzero::U256 as NonZeroU256}, + number::{ + conversions::{big_decimal_to_big_rational, u256_to_big_rational}, + nonzero::U256 as NonZeroU256, + }, std::{collections::HashMap, sync::Arc}, web3::{ethabi::Token, types::CallRequest}, }; #[async_trait::async_trait] pub trait TradeVerifying: Send + Sync + 'static { - /// Verifies if the proposed [`Trade`] actually fulfills the [`PriceQuery`]. + /// Verifies if the proposed [`TradeKind`] actually fulfills the + /// [`PriceQuery`]. async fn verify( &self, query: &PriceQuery, verification: &Verification, - trade: Trade, + trade: TradeKind, ) -> Result; } @@ -50,38 +60,43 @@ pub struct TradeVerifier { settlement: GPv2Settlement, native_token: H160, quote_inaccuracy_limit: BigRational, + domain_separator: DomainSeparator, } impl TradeVerifier { const DEFAULT_GAS: u64 = 8_000_000; const TRADER_IMPL: H160 = addr!("0000000000000000000000000000000000010000"); - pub fn new( + pub async fn new( web3: Web3, simulator: Arc, code_fetcher: Arc, block_stream: CurrentBlockWatcher, settlement: H160, native_token: H160, - quote_inaccuracy_limit: f64, - ) -> Self { - Self { + quote_inaccuracy_limit: BigDecimal, + ) -> Result { + let settlement_contract = GPv2Settlement::at(&web3, settlement); + let domain_separator = + DomainSeparator(settlement_contract.domain_separator().call().await?.0); + Ok(Self { simulator, code_fetcher, block_stream, - settlement: GPv2Settlement::at(&web3, settlement), + settlement: settlement_contract, native_token, - quote_inaccuracy_limit: BigRational::from_float(quote_inaccuracy_limit) - .expect("can represent all finite values"), + quote_inaccuracy_limit: big_decimal_to_big_rational("e_inaccuracy_limit), web3, - } + domain_separator, + }) } async fn verify_inner( &self, query: &PriceQuery, verification: &Verification, - trade: &Trade, + trade: &TradeKind, + out_amount: &U256, ) -> Result { if verification.from.is_zero() { // Don't waste time on common simulations which will always fail. @@ -92,17 +107,33 @@ impl TradeVerifier { // Use `tx_origin` if response indicates that a special address is needed for // the simulation to pass. Otherwise just use the solver address. - let solver = trade.tx_origin.unwrap_or(trade.solver); + let solver = trade.tx_origin().unwrap_or(trade.solver()); let solver = dummy_contract!(Solver, solver); - let settlement = encode_settlement(query, verification, trade, self.native_token); - let settlement = add_balance_queries( - settlement, + let (tokens, clearing_prices) = match trade { + TradeKind::Legacy(_) => { + let tokens = vec![query.sell_token, query.buy_token]; + let prices = match query.kind { + OrderKind::Sell => vec![*out_amount, query.in_amount.get()], + OrderKind::Buy => vec![query.in_amount.get(), *out_amount], + }; + (tokens, prices) + } + TradeKind::Regular(trade) => trade.clearing_prices.iter().map(|e| e.to_owned()).unzip(), + }; + + let settlement = encode_settlement( query, verification, - self.settlement.address(), - &solver, - ); + trade, + &tokens, + &clearing_prices, + out_amount, + self.native_token, + &self.domain_separator, + )?; + + let settlement = add_balance_queries(settlement, query, verification, &solver); let settlement = self .settlement @@ -117,7 +148,7 @@ impl TradeVerifier { let sell_amount = match query.kind { OrderKind::Sell => query.in_amount.get(), - OrderKind::Buy => trade.out_amount, + OrderKind::Buy => *out_amount, }; let simulation = solver @@ -127,8 +158,8 @@ impl TradeVerifier { verification.from, query.sell_token, sell_amount, - query.buy_token, self.native_token, + tokens.clone(), verification.receiver, Bytes(settlement.data.unwrap().0), // only if the user did not provide pre-interactions is it safe @@ -173,11 +204,11 @@ impl TradeVerifier { // for a different `tx.origin` we need to pretend these // quotes actually simulated successfully to not lose these competitive quotes // when we enable quote verification in prod. - if trade.tx_origin == Some(H160::zero()) { + if trade.tx_origin() == Some(H160::zero()) { let estimate = Estimate { - out_amount: trade.out_amount, - gas: trade.gas_estimate.context("no gas estimate")?, - solver: trade.solver, + out_amount: *out_amount, + gas: trade.gas_estimate().context("no gas estimate")?, + solver: trade.solver(), verified: true, }; tracing::warn!( @@ -189,7 +220,7 @@ impl TradeVerifier { } }; - let mut summary = SettleOutput::decode(&output?, query.kind) + let mut summary = SettleOutput::decode(&output?, query.kind, &tokens) .context("could not decode simulation output") .map_err(Error::SimulationFailed)?; @@ -205,32 +236,42 @@ impl TradeVerifier { // It looks like the contract lost a lot of sell tokens but only because it was // the trader and had to pay for the trade. Adjust tokens lost downward. if verification.from == self.settlement.address() { - summary.sell_tokens_lost -= u256_to_big_rational(&sell_amount); + summary + .tokens_lost + .entry(query.sell_token) + .and_modify(|balance| *balance -= u256_to_big_rational(&sell_amount)); } // It looks like the contract gained a lot of buy tokens (negative loss) but // only because it was the receiver and got the payout. Adjust the tokens lost // upward. if verification.receiver == self.settlement.address() { - summary.buy_tokens_lost += u256_to_big_rational(&buy_amount); + summary + .tokens_lost + .entry(query.buy_token) + .and_modify(|balance| *balance += u256_to_big_rational(&buy_amount)); } } tracing::debug!( - lost_buy_amount = %summary.buy_tokens_lost, - lost_sell_amount = %summary.sell_tokens_lost, - gas_diff = ?trade.gas_estimate.unwrap_or_default().abs_diff(summary.gas_used.as_u64()), + tokens_lost = ?summary.tokens_lost, + gas_diff = ?trade.gas_estimate().unwrap_or_default().abs_diff(summary.gas_used.as_u64()), time = ?start.elapsed(), - promised_out_amount = ?trade.out_amount, + promised_out_amount = ?out_amount, verified_out_amount = ?summary.out_amount, - promised_gas = trade.gas_estimate, + promised_gas = trade.gas_estimate(), verified_gas = ?summary.gas_used, - out_diff = ?trade.out_amount.abs_diff(summary.out_amount), + out_diff = ?out_amount.abs_diff(summary.out_amount), ?query, ?verification, "verified quote", ); - ensure_quote_accuracy(&self.quote_inaccuracy_limit, query, trade.solver, &summary) + ensure_quote_accuracy( + &self.quote_inaccuracy_limit, + query, + trade.solver(), + &summary, + ) } /// Configures all the state overrides that are needed to mock the given @@ -238,7 +279,7 @@ impl TradeVerifier { async fn prepare_state_overrides( &self, verification: &Verification, - trade: &Trade, + trade: &TradeKind, ) -> Result> { // Set up mocked trader. let mut overrides = hashmap! { @@ -273,10 +314,13 @@ impl TradeVerifier { // If the trade requires a special tx.origin we also need to fake the // authenticator and tx origin balance. - if trade.tx_origin.is_some_and(|origin| origin != trade.solver) { + if trade + .tx_origin() + .is_some_and(|origin| origin != trade.solver()) + { let (authenticator, balance) = futures::join!( self.settlement.authenticator().call(), - self.web3.eth().balance(trade.solver, None) + self.web3.eth().balance(trade.solver(), None) ); let authenticator = authenticator.context("could not fetch authenticator")?; overrides.insert( @@ -289,7 +333,7 @@ impl TradeVerifier { let balance = balance.context("could not fetch balance")?; solver_override.balance = Some(balance); } - overrides.insert(trade.tx_origin.unwrap_or(trade.solver), solver_override); + overrides.insert(trade.tx_origin().unwrap_or(trade.solver()), solver_override); Ok(overrides) } @@ -301,16 +345,27 @@ impl TradeVerifying for TradeVerifier { &self, query: &PriceQuery, verification: &Verification, - trade: Trade, + trade: TradeKind, ) -> Result { - match self.verify_inner(query, verification, &trade).await { + let out_amount = trade + .out_amount( + &query.buy_token, + &query.sell_token, + &query.in_amount.get(), + &query.kind, + ) + .context("failed to compute trade out amount")?; + match self + .verify_inner(query, verification, &trade, &out_amount) + .await + { Ok(verified) => Ok(verified), - Err(Error::SimulationFailed(err)) => match trade.gas_estimate { + Err(Error::SimulationFailed(err)) => match trade.gas_estimate() { Some(gas) => { let estimate = Estimate { - out_amount: trade.out_amount, + out_amount, gas, - solver: trade.solver, + solver: trade.solver(), verified: false, }; tracing::warn!( @@ -341,20 +396,25 @@ fn encode_interactions(interactions: &[Interaction]) -> Vec interactions.iter().map(|i| i.encode()).collect() } +#[allow(clippy::too_many_arguments)] fn encode_settlement( query: &PriceQuery, verification: &Verification, - trade: &Trade, + trade: &TradeKind, + tokens: &[H160], + clearing_prices: &[U256], + out_amount: &U256, native_token: H160, -) -> EncodedSettlement { - let mut trade_interactions = encode_interactions(&trade.interactions); + domain_separator: &DomainSeparator, +) -> Result { + let mut trade_interactions = encode_interactions(&trade.interactions()); if query.buy_token == BUY_ETH_ADDRESS { // Because the `driver` manages `WETH` unwraps under the hood the `TradeFinder` // does not have to emit unwraps to pay out `ETH` in a trade. // However, for the simulation to be successful this has to happen so we do it // ourselves here. let buy_amount = match query.kind { - OrderKind::Sell => trade.out_amount, + OrderKind::Sell => *out_amount, OrderKind::Buy => query.in_amount.get(), }; let weth = dummy_contract!(WETH9, native_token); @@ -363,12 +423,40 @@ fn encode_settlement( tracing::trace!("adding unwrap interaction for paying out ETH"); } - let tokens = vec![query.sell_token, query.buy_token]; - let clearing_prices = match query.kind { - OrderKind::Sell => vec![trade.out_amount, query.in_amount.get()], - OrderKind::Buy => vec![query.in_amount.get(), trade.out_amount], - }; + let fake_trade = encode_fake_trade(query, verification, out_amount, tokens)?; + let mut trades = vec![fake_trade]; + if let TradeKind::Regular(trade) = trade { + trades.extend(encode_jit_orders( + &trade.jit_orders, + tokens, + domain_separator, + )?); + } + + let pre_interactions = [ + verification.pre_interactions.clone(), + trade.pre_interactions(), + ] + .concat(); + Ok(EncodedSettlement { + tokens: tokens.to_vec(), + clearing_prices: clearing_prices.to_vec(), + trades, + interactions: [ + encode_interactions(&pre_interactions), + trade_interactions, + encode_interactions(&verification.post_interactions), + ], + }) +} + +fn encode_fake_trade( + query: &PriceQuery, + verification: &Verification, + out_amount: &U256, + tokens: &[H160], +) -> Result { // Configure the most disadvantageous trade possible (while taking possible // overflows into account). Should the trader not receive the amount promised by // the [`Trade`] the simulation will still work and we can compute the actual @@ -376,7 +464,7 @@ fn encode_settlement( let (sell_amount, buy_amount) = match query.kind { OrderKind::Sell => (query.in_amount.get(), 0.into()), OrderKind::Buy => ( - trade.out_amount.max(U256::from(u128::MAX)), + (*out_amount).max(U256::from(u128::MAX)), query.in_amount.get(), ), }; @@ -400,21 +488,96 @@ fn encode_settlement( &fake_order, &fake_signature, verification.from, - 0, - 1, + // the tokens set length is small so the linear search is acceptable + tokens + .iter() + .position(|token| token == &query.sell_token) + .context("missing sell token index")?, + tokens + .iter() + .position(|token| token == &query.buy_token) + .context("missing buy token index")?, &query.in_amount.get(), ); - EncodedSettlement { - tokens, - clearing_prices, - trades: vec![encoded_trade], - interactions: [ - encode_interactions(&verification.pre_interactions), - trade_interactions, - encode_interactions(&verification.post_interactions), - ], - } + Ok(encoded_trade) +} + +fn encode_jit_orders( + jit_orders: &[dto::JitOrder], + tokens: &[H160], + domain_separator: &DomainSeparator, +) -> Result, Error> { + jit_orders + .iter() + .map(|jit_order| { + let order_data = OrderData { + sell_token: jit_order.sell_token, + buy_token: jit_order.buy_token, + receiver: Some(jit_order.receiver), + sell_amount: jit_order.sell_amount, + buy_amount: jit_order.buy_amount, + valid_to: jit_order.valid_to, + app_data: jit_order.app_data, + fee_amount: 0.into(), + kind: match &jit_order.side { + dto::Side::Buy => OrderKind::Buy, + dto::Side::Sell => OrderKind::Sell, + }, + partially_fillable: jit_order.partially_fillable, + sell_token_balance: jit_order.sell_token_source, + buy_token_balance: jit_order.buy_token_destination, + }; + let (owner, signature) = + recover_jit_order_owner(jit_order, &order_data, domain_separator)?; + + Ok(encode_trade( + &order_data, + &signature, + owner, + // the tokens set length is small so the linear search is acceptable + tokens + .iter() + .position(|token| token == &jit_order.sell_token) + .context("missing jit order sell token index")?, + tokens + .iter() + .position(|token| token == &jit_order.buy_token) + .context("missing jit order buy token index")?, + &jit_order.executed_amount, + )) + }) + .collect::, Error>>() +} + +/// Recovers the owner and signature from a `JitOrder`. +fn recover_jit_order_owner( + jit_order: &JitOrder, + order_data: &OrderData, + domain_separator: &DomainSeparator, +) -> Result<(H160, Signature), Error> { + let (owner, signature) = match jit_order.signing_scheme { + SigningScheme::Eip1271 => { + let (owner, signature) = jit_order.signature.split_at(20); + let owner = H160::from_slice(owner); + let signature = Signature::from_bytes(jit_order.signing_scheme, signature)?; + (owner, signature) + } + SigningScheme::PreSign => { + let owner = H160::from_slice(&jit_order.signature); + let signature = Signature::from_bytes(jit_order.signing_scheme, Vec::new().as_slice())?; + (owner, signature) + } + _ => { + let signature = Signature::from_bytes(jit_order.signing_scheme, &jit_order.signature)?; + let owner = signature + .recover(domain_separator, &order_data.hash_struct())? + .context("could not recover the owner")? + .signer; + (owner, signature) + } + }; + Ok((owner, signature)) } /// Adds the interactions that are only needed to query important balances @@ -424,7 +587,6 @@ fn add_balance_queries( mut settlement: EncodedSettlement, query: &PriceQuery, verification: &Verification, - settlement_contract: H160, solver: &Solver, ) -> EncodedSettlement { let (token, owner) = match query.kind { @@ -438,14 +600,14 @@ fn add_balance_queries( (query.buy_token, receiver) } - // track how much `sell_token` the settlement contract actually spent - OrderKind::Buy => (query.sell_token, settlement_contract), + // track how much `sell_token` the `from` address actually spent + OrderKind::Buy => (query.sell_token, verification.from), }; let query_balance = solver.methods().store_balance(token, owner, true); let query_balance = Bytes(query_balance.tx.data.unwrap().0); let interaction = (solver.address(), 0.into(), query_balance); - // query balance right after we receive all `sell_token` - settlement.interactions[1].insert(0, interaction.clone()); + // query balance query at the end of pre-interactions + settlement.interactions[0].push(interaction.clone()); // query balance right after we payed out all `buy_token` settlement.interactions[2].insert(0, interaction); settlement @@ -459,16 +621,12 @@ struct SettleOutput { /// `out_amount` perceived by the trader (sell token for buy orders or buy /// token for sell order) out_amount: U256, - /// Difference in buy tokens of the settlement contract before and after the - /// trade. - buy_tokens_lost: BigRational, - /// Difference in sell tokens of the settlement contract before and after - /// the trade. - sell_tokens_lost: BigRational, + /// Tokens difference of the settlement contract before and after the trade. + tokens_lost: HashMap, } impl SettleOutput { - fn decode(output: &[u8], kind: OrderKind) -> Result { + fn decode(output: &[u8], kind: OrderKind, tokens_vec: &[H160]) -> Result { let function = Solver::raw_contract() .interface .abi @@ -477,14 +635,29 @@ impl SettleOutput { let tokens = function.decode_output(output).context("decode")?; let (gas_used, balances): (U256, Vec) = Tokenize::from_token(Token::Tuple(tokens))?; - let settlement_sell_balance_before = u256_to_big_rational(&balances[0]); - let settlement_buy_balance_before = u256_to_big_rational(&balances[1]); - - let trader_balance_before = balances[2]; - let trader_balance_after = balances[3]; + // The balances are stored in the following order: + // [...tokens_before, user_balance_before, user_balance_after, ...tokens_after] + let mut i = 0; + let mut tokens_lost = HashMap::new(); + // Get settlement contract balances before the trade + for token in tokens_vec.iter() { + let balance_before = u256_to_big_rational(&balances[i]); + tokens_lost.insert(*token, balance_before); + i += 1; + } - let settlement_sell_balance_after = u256_to_big_rational(&balances[4]); - let settlement_buy_balance_after = u256_to_big_rational(&balances[5]); + let trader_balance_before = balances[i]; + let trader_balance_after = balances[i + 1]; + i += 2; + + // Get settlement contract balances after the trade + for token in tokens_vec.iter() { + let balance_after = u256_to_big_rational(&balances[i]); + tokens_lost + .entry(*token) + .and_modify(|balance_before| *balance_before -= balance_after); + i += 1; + } let out_amount = match kind { // for sell orders we track the buy_token amount which increases during the settlement @@ -497,8 +670,7 @@ impl SettleOutput { Ok(SettleOutput { gas_used, out_amount, - buy_tokens_lost: settlement_buy_balance_before - settlement_buy_balance_after, - sell_tokens_lost: settlement_sell_balance_before - settlement_sell_balance_after, + tokens_lost, }) } } @@ -510,16 +682,29 @@ fn ensure_quote_accuracy( query: &PriceQuery, solver: H160, summary: &SettleOutput, -) -> Result { +) -> std::result::Result { // amounts verified by the simulation let (sell_amount, buy_amount) = match query.kind { OrderKind::Buy => (summary.out_amount, query.in_amount.get()), OrderKind::Sell => (query.in_amount.get(), summary.out_amount), }; - - if summary.sell_tokens_lost >= inaccuracy_limit * u256_to_big_rational(&sell_amount) - || summary.buy_tokens_lost >= inaccuracy_limit * u256_to_big_rational(&buy_amount) - { + let (sell_amount, buy_amount) = ( + u256_to_big_rational(&sell_amount), + u256_to_big_rational(&buy_amount), + ); + let sell_token_lost_limit = inaccuracy_limit * &sell_amount; + let buy_token_lost_limit = inaccuracy_limit * &buy_amount; + + let sell_token_lost = summary + .tokens_lost + .get(&query.sell_token) + .context("summary sell token is missing")?; + let buy_token_lost = summary + .tokens_lost + .get(&query.buy_token) + .context("summary buy token is missing")?; + + if *sell_token_lost >= sell_token_lost_limit || *buy_token_lost >= buy_token_lost_limit { return Err(Error::TooInaccurate); } @@ -553,27 +738,71 @@ enum Error { #[cfg(test)] mod tests { - use super::*; + use {super::*, std::str::FromStr}; #[test] fn discards_inaccurate_quotes() { // let's use 0.5 as the base case to avoid rounding issues introduced by float // conversion - let low_threshold = BigRational::from_float(0.5).unwrap(); - let high_threshold = BigRational::from_float(0.51).unwrap(); + let low_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.5").unwrap()); + let high_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.51").unwrap()); + + let sell_token = H160([1u8; 20]); + let buy_token = H160([2u8; 20]); let query = PriceQuery { in_amount: 1_000.try_into().unwrap(), kind: OrderKind::Sell, - sell_token: H160::zero(), - buy_token: H160::zero(), + sell_token, + buy_token, + }; + + // buy token is lost + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(500.into()), + }; + let summary = SettleOutput { + gas_used: 0.into(), + out_amount: 2_000.into(), + tokens_lost, + }; + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + assert!(matches!(estimate, Err(Error::SimulationFailed(_)))); + + // sell token is lost + let tokens_lost = hashmap! { + buy_token => BigRational::from_integer(0.into()), + }; + let summary = SettleOutput { + gas_used: 0.into(), + out_amount: 2_000.into(), + tokens_lost, + }; + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + assert!(matches!(estimate, Err(Error::SimulationFailed(_)))); + + // everything is in-place + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(400.into()), + buy_token => BigRational::from_integer(0.into()), + }; + let summary = SettleOutput { + gas_used: 0.into(), + out_amount: 2_000.into(), + tokens_lost, + }; + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + assert!(estimate.is_ok()); + + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(500.into()), + buy_token => BigRational::from_integer(0.into()), }; let sell_more = SettleOutput { gas_used: 0.into(), out_amount: 2_000.into(), - buy_tokens_lost: BigRational::from_integer(0.into()), - sell_tokens_lost: BigRational::from_integer(500.into()), + tokens_lost, }; let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &sell_more); @@ -583,11 +812,15 @@ mod tests { let estimate = ensure_quote_accuracy(&high_threshold, &query, H160::zero(), &sell_more); assert!(estimate.is_ok()); + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(0.into()), + buy_token => BigRational::from_integer(1_000.into()), + }; + let pay_out_more = SettleOutput { gas_used: 0.into(), out_amount: 2_000.into(), - buy_tokens_lost: BigRational::from_integer(1_000.into()), - sell_tokens_lost: BigRational::from_integer(0.into()), + tokens_lost, }; let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &pay_out_more); @@ -597,21 +830,29 @@ mod tests { let estimate = ensure_quote_accuracy(&high_threshold, &query, H160::zero(), &pay_out_more); assert!(estimate.is_ok()); + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer((-500).into()), + buy_token => BigRational::from_integer(0.into()), + }; + let sell_less = SettleOutput { gas_used: 0.into(), out_amount: 2_000.into(), - buy_tokens_lost: BigRational::from_integer(0.into()), - sell_tokens_lost: BigRational::from_integer((-500).into()), + tokens_lost, }; // Ending up with surplus in the buffers is always fine let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &sell_less); assert!(estimate.is_ok()); + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(0.into()), + buy_token => BigRational::from_integer((-1_000).into()), + }; + let pay_out_less = SettleOutput { gas_used: 0.into(), out_amount: 2_000.into(), - buy_tokens_lost: BigRational::from_integer((-1_000).into()), - sell_tokens_lost: BigRational::from_integer(0.into()), + tokens_lost, }; // Ending up with surplus in the buffers is always fine let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &pay_out_less); diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 60784e2e4b..8aef3be293 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -4,7 +4,15 @@ use { crate::{ price_estimation::{PriceEstimationError, Query}, request_sharing::{BoxRequestSharing, RequestSharing}, - trade_finding::{Interaction, Quote, Trade, TradeError, TradeFinding}, + trade_finding::{ + Interaction, + LegacyTrade, + Quote, + Trade, + TradeError, + TradeFinding, + TradeKind, + }, }, anyhow::{anyhow, Context}, ethrpc::block_stream::CurrentBlockWatcher, @@ -20,7 +28,7 @@ pub struct ExternalTradeFinder { /// Utility to make sure no 2 identical requests are in-flight at the same /// time. Instead of issuing a duplicated request this awaits the /// response of the in-flight request. - sharing: BoxRequestSharing>, + sharing: BoxRequestSharing>, /// Client to issue http requests with. client: Client, @@ -50,7 +58,7 @@ impl ExternalTradeFinder { /// Queries the `/quote` endpoint of the configured driver and deserializes /// the result into a Quote or Trade. - async fn shared_query(&self, query: &Query) -> Result { + async fn shared_query(&self, query: &Query) -> Result { let fut = move |query: &Query| { let order = dto::Order { sell_token: query.sell_token, @@ -93,19 +101,15 @@ impl ExternalTradeFinder { .text() .await .map_err(|err| PriceEstimationError::EstimatorInternal(anyhow!(err)))?; - let quote = serde_json::from_str::(&text).map_err(|err| { - if let Ok(err) = serde_json::from_str::(&text) { - PriceEstimationError::from(err) - } else { - PriceEstimationError::EstimatorInternal(anyhow!(err)) - } - })?; - match quote { - dto::QuoteKind::Legacy(quote) => Ok(Trade::from(quote)), - dto::QuoteKind::Regular(_) => Err(PriceEstimationError::EstimatorInternal( - anyhow!("Quote with JIT orders is not currently supported"), - )), - } + serde_json::from_str::(&text) + .map(TradeKind::from) + .map_err(|err| { + if let Ok(err) = serde_json::from_str::(&text) { + PriceEstimationError::from(err) + } else { + PriceEstimationError::EstimatorInternal(anyhow!(err)) + } + }) } .boxed() }; @@ -117,7 +121,16 @@ impl ExternalTradeFinder { } } -impl From for Trade { +impl From for TradeKind { + fn from(quote: dto::QuoteKind) -> Self { + match quote { + dto::QuoteKind::Legacy(quote) => TradeKind::Legacy(quote.into()), + dto::QuoteKind::Regular(quote) => TradeKind::Regular(quote.into()), + } + } +} + +impl From for LegacyTrade { fn from(quote: dto::LegacyQuote) -> Self { Self { out_amount: quote.amount, @@ -137,6 +150,36 @@ impl From for Trade { } } +impl From for Trade { + fn from(quote: dto::Quote) -> Self { + Self { + clearing_prices: quote.clearing_prices, + gas_estimate: quote.gas, + pre_interactions: quote + .pre_interactions + .into_iter() + .map(|interaction| Interaction { + target: interaction.target, + value: interaction.value, + data: interaction.call_data, + }) + .collect(), + interactions: quote + .interactions + .into_iter() + .map(|interaction| Interaction { + target: interaction.target, + value: interaction.value, + data: interaction.call_data, + }) + .collect(), + solver: quote.solver, + tx_origin: quote.tx_origin, + jit_orders: quote.jit_orders, + } + } +} + impl From for PriceEstimationError { fn from(value: dto::Error) -> Self { match value.kind.as_str() { @@ -163,22 +206,29 @@ impl TradeFinding for ExternalTradeFinder { // reuse the same logic here. let trade = self.get_trade(query).await?; let gas_estimate = trade - .gas_estimate + .gas_estimate() .context("no gas estimate") .map_err(TradeError::Other)?; Ok(Quote { - out_amount: trade.out_amount, + out_amount: trade + .out_amount( + &query.buy_token, + &query.sell_token, + &query.in_amount.get(), + &query.kind, + ) + .map_err(TradeError::Other)?, gas_estimate, - solver: trade.solver, + solver: trade.solver(), }) } - async fn get_trade(&self, query: &Query) -> Result { + async fn get_trade(&self, query: &Query) -> Result { self.shared_query(query).await } } -mod dto { +pub(crate) mod dto { use { app_data::AppDataHash, bytes_hex::BytesHex, diff --git a/crates/shared/src/trade_finding/mod.rs b/crates/shared/src/trade_finding/mod.rs index 221964b0e7..f8e589c467 100644 --- a/crates/shared/src/trade_finding/mod.rs +++ b/crates/shared/src/trade_finding/mod.rs @@ -4,12 +4,19 @@ pub mod external; use { - crate::price_estimation::{PriceEstimationError, Query}, - anyhow::Result, + crate::{ + conversions::U256Ext, + price_estimation::{PriceEstimationError, Query}, + trade_finding::external::dto, + }, + anyhow::{Context, Result}, derive_more::Debug, ethcontract::{contract::MethodBuilder, tokens::Tokenize, web3::Transport, Bytes, H160, U256}, - model::interaction::InteractionData, + model::{interaction::InteractionData, order::OrderKind}, + num::CheckedDiv, + number::conversions::big_rational_to_u256, serde::Serialize, + std::{collections::HashMap, ops::Mul}, thiserror::Error, }; @@ -21,7 +28,7 @@ use { #[async_trait::async_trait] pub trait TradeFinding: Send + Sync + 'static { async fn get_quote(&self, query: &Query) -> Result; - async fn get_trade(&self, query: &Query) -> Result; + async fn get_trade(&self, query: &Query) -> Result; } /// A quote. @@ -32,9 +39,73 @@ pub struct Quote { pub solver: H160, } -/// A trade. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum TradeKind { + Legacy(LegacyTrade), + Regular(Trade), +} + +impl TradeKind { + pub fn gas_estimate(&self) -> Option { + match self { + TradeKind::Legacy(trade) => trade.gas_estimate, + TradeKind::Regular(trade) => trade.gas_estimate, + } + } + + pub fn solver(&self) -> H160 { + match self { + TradeKind::Legacy(trade) => trade.solver, + TradeKind::Regular(trade) => trade.solver, + } + } + + pub fn tx_origin(&self) -> Option { + match self { + TradeKind::Legacy(trade) => trade.tx_origin, + TradeKind::Regular(trade) => trade.tx_origin, + } + } + + pub fn out_amount( + &self, + buy_token: &H160, + sell_token: &H160, + in_amount: &U256, + order_kind: &OrderKind, + ) -> Result { + match self { + TradeKind::Legacy(trade) => Ok(trade.out_amount), + TradeKind::Regular(trade) => { + trade.out_amount(buy_token, sell_token, in_amount, order_kind) + } + } + } + + pub fn interactions(&self) -> Vec { + match self { + TradeKind::Legacy(trade) => trade.interactions.clone(), + TradeKind::Regular(trade) => trade.interactions.clone(), + } + } + + pub fn pre_interactions(&self) -> Vec { + match self { + TradeKind::Legacy(_) => Vec::new(), + TradeKind::Regular(trade) => trade.pre_interactions.clone(), + } + } +} + +impl Default for TradeKind { + fn default() -> Self { + TradeKind::Legacy(LegacyTrade::default()) + } +} + +/// A legacy trade. #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Trade { +pub struct LegacyTrade { /// For sell orders: how many buy_tokens this trade will produce. /// For buy orders: how many sell_tokens this trade will cost. pub out_amount: U256, @@ -49,6 +120,61 @@ pub struct Trade { pub tx_origin: Option, } +/// A trade with JIT orders. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Trade { + pub clearing_prices: HashMap, + /// How many units of gas this trade will roughly cost. + pub gas_estimate: Option, + /// The onchain calls to run before sending user funds to the settlement + /// contract. + pub pre_interactions: Vec, + /// Interactions needed to produce the expected trade amount. + pub interactions: Vec, + /// Which solver provided this trade. + pub solver: H160, + /// If this is set the quote verification need to use this as the + /// `tx.origin` to make the quote pass the simulation. + pub tx_origin: Option, + pub jit_orders: Vec, +} + +impl Trade { + pub fn out_amount( + &self, + buy_token: &H160, + sell_token: &H160, + in_amount: &U256, + order_kind: &OrderKind, + ) -> Result { + let sell_price = self + .clearing_prices + .get(sell_token) + .context("clearing sell price missing")? + .to_big_rational(); + let buy_price = self + .clearing_prices + .get(buy_token) + .context("clearing buy price missing")? + .to_big_rational(); + let order_amount = in_amount.to_big_rational(); + + let out_amount = match order_kind { + OrderKind::Sell => order_amount + .mul(&sell_price) + .checked_div(&buy_price) + .context("div by zero: buy price")? + .ceil(), /* `ceil` is used to compute buy amount only: https://github.com/cowprotocol/contracts/blob/main/src/contracts/GPv2Settlement.sol#L389-L411 */ + OrderKind::Buy => order_amount + .mul(&buy_price) + .checked_div(&sell_price) + .context("div by zero: sell price")?, + }; + + big_rational_to_u256(&out_amount).context("out amount is not a valid U256") + } +} + /// Data for a raw GPv2 interaction. #[derive(Clone, PartialEq, Eq, Hash, Default, Serialize, Debug)] pub struct Interaction { From 1413398f574b3a62ec3b63c61752c1c0b912affc Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 2 Dec 2024 13:28:02 +0000 Subject: [PATCH 02/25] Driver sends quote response with JIT orders (#3103) # Description Closes task n3 from https://github.com/cowprotocol/services/issues/3082 by start sending the new `/quote` response with the updated fields that include clearing prices instead of the executed amount, jit orders and pre-interactions. # Changes - [ ] Updated driver's `/quote` response which is now supported by the trade verifier. ## How to test New driver's and services' e2e tests. --- .../src/domain/competition/order/mod.rs | 1 + .../src/domain/competition/order/signature.rs | 11 + .../domain/competition/solution/encoding.rs | 14 +- .../src/domain/competition/solution/mod.rs | 27 +- .../domain/competition/solution/settlement.rs | 4 +- crates/driver/src/domain/quote.rs | 54 +-- .../src/infra/api/routes/quote/dto/quote.rs | 101 ++++- .../driver/src/infra/api/routes/quote/mod.rs | 2 +- .../driver/src/infra/solver/dto/solution.rs | 5 +- crates/driver/src/tests/cases/quote.rs | 38 +- crates/driver/src/tests/setup/blockchain.rs | 2 +- crates/driver/src/tests/setup/mod.rs | 115 ++++- crates/driver/src/tests/setup/solver.rs | 29 +- crates/e2e/src/setup/colocation.rs | 2 + crates/e2e/src/setup/services.rs | 2 + crates/e2e/tests/e2e/buffers.rs | 1 + crates/e2e/tests/e2e/cow_amm.rs | 393 +++++++++++++++++- crates/e2e/tests/e2e/jit_orders.rs | 1 + crates/e2e/tests/e2e/limit_orders.rs | 3 + crates/e2e/tests/e2e/liquidity.rs | 1 + crates/e2e/tests/e2e/order_cancellation.rs | 1 + crates/e2e/tests/e2e/solver_competition.rs | 2 + crates/solvers/openapi.yml | 2 + 23 files changed, 723 insertions(+), 88 deletions(-) diff --git a/crates/driver/src/domain/competition/order/mod.rs b/crates/driver/src/domain/competition/order/mod.rs index 0235e41091..0088d36f07 100644 --- a/crates/driver/src/domain/competition/order/mod.rs +++ b/crates/driver/src/domain/competition/order/mod.rs @@ -387,6 +387,7 @@ pub struct Jit { pub buy: eth::Asset, pub receiver: eth::Address, pub valid_to: util::Timestamp, + pub partially_fillable: bool, pub app_data: AppData, pub side: Side, pub sell_token_balance: SellTokenBalance, diff --git a/crates/driver/src/domain/competition/order/signature.rs b/crates/driver/src/domain/competition/order/signature.rs index 31e4e62c04..dd51692a45 100644 --- a/crates/driver/src/domain/competition/order/signature.rs +++ b/crates/driver/src/domain/competition/order/signature.rs @@ -42,6 +42,17 @@ pub enum Scheme { PreSign, } +impl Scheme { + pub fn to_boundary_scheme(&self) -> model::signature::SigningScheme { + match self { + Scheme::Eip712 => model::signature::SigningScheme::Eip712, + Scheme::EthSign => model::signature::SigningScheme::EthSign, + Scheme::Eip1271 => model::signature::SigningScheme::Eip1271, + Scheme::PreSign => model::signature::SigningScheme::PreSign, + } + } +} + pub fn domain_separator( chain_id: chain::Id, verifying_contract: eth::ContractAddress, diff --git a/crates/driver/src/domain/competition/solution/encoding.rs b/crates/driver/src/domain/competition/solution/encoding.rs index 9e610e10a5..1872131827 100644 --- a/crates/driver/src/domain/competition/solution/encoding.rs +++ b/crates/driver/src/domain/competition/solution/encoding.rs @@ -47,13 +47,13 @@ pub fn tx( let mut native_unwrap = eth::TokenAmount(eth::U256::zero()); // Encode uniform clearing price vector - for asset in solution + for (token, amount) in solution .clearing_prices() - .iter() - .sorted_by_cached_key(|asset| asset.token) + .into_iter() + .sorted_by_cached_key(|(token, _amount)| *token) { - tokens.push(asset.token.into()); - clearing_prices.push(asset.amount.into()); + tokens.push(token.into()); + clearing_prices.push(amount); } // Encode trades with custom clearing prices @@ -312,7 +312,7 @@ struct Flags { buy_token_balance: order::BuyTokenBalance, } -mod codec { +pub mod codec { use crate::domain::{competition::order, eth}; // cf. https://github.com/cowprotocol/contracts/blob/v1.5.0/src/contracts/libraries/GPv2Trade.sol#L16 @@ -392,7 +392,7 @@ mod codec { ) } - pub(super) fn signature(signature: &order::Signature) -> super::Bytes> { + pub fn signature(signature: &order::Signature) -> super::Bytes> { match signature.scheme { order::signature::Scheme::Eip712 | order::signature::Scheme::EthSign => { signature.data.clone() diff --git a/crates/driver/src/domain/competition/solution/mod.rs b/crates/driver/src/domain/competition/solution/mod.rs index 531568f19f..2f55562900 100644 --- a/crates/driver/src/domain/competition/solution/mod.rs +++ b/crates/driver/src/domain/competition/solution/mod.rs @@ -174,6 +174,10 @@ impl Solution { &self.interactions } + pub fn pre_interactions(&self) -> &[eth::Interaction] { + &self.pre_interactions + } + /// The solver which generated this solution. pub fn solver(&self) -> &Solver { &self.solver @@ -401,11 +405,8 @@ impl Solution { /// /// The rule which relates two prices for tokens X and Y is: /// amount_x * price_x = amount_y * price_y - pub fn clearing_prices(&self) -> Vec { - let prices = self.prices.iter().map(|(&token, &amount)| eth::Asset { - token, - amount: amount.into(), - }); + pub fn clearing_prices(&self) -> Prices { + let prices = self.prices.clone(); if self.user_trades().any(|trade| trade.order().buys_eth()) { // The solution contains an order which buys ETH. Solvers only produce solutions @@ -418,28 +419,26 @@ impl Solution { // If no order trades WETH, the WETH price is not necessary, only the ETH // price is needed. Remove the unneeded WETH price, which slightly reduces // gas used by the settlement. - let mut prices = if self.user_trades().all(|trade| { + let mut prices: Prices = if self.user_trades().all(|trade| { trade.order().sell.token != self.weth.0 && trade.order().buy.token != self.weth.0 }) { prices - .filter(|price| price.token != self.weth.0) - .collect_vec() + .into_iter() + .filter(|(token, _price)| *token != self.weth.0) + .collect() } else { - prices.collect_vec() + prices }; // Add a clearing price for ETH equal to WETH. - prices.push(eth::Asset { - token: eth::ETH_TOKEN, - amount: self.prices[&self.weth.into()].to_owned().into(), - }); + prices.insert(eth::ETH_TOKEN, self.prices[&self.weth.into()].to_owned()); return prices; } // TODO: We should probably filter out all unused prices to save gas. - prices.collect_vec() + prices } /// Clearing price for the given token. diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index c69c582317..0ea089685b 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -308,8 +308,8 @@ impl Settlement { pub fn prices(&self) -> HashMap { self.solution .clearing_prices() - .iter() - .map(|asset| (asset.token, asset.amount)) + .into_iter() + .map(|(token, amount)| (token, amount.into())) .collect() } } diff --git a/crates/driver/src/domain/quote.rs b/crates/driver/src/domain/quote.rs index e63b8e0040..cbb2776bc2 100644 --- a/crates/driver/src/domain/quote.rs +++ b/crates/driver/src/domain/quote.rs @@ -13,52 +13,37 @@ use { blockchain::{self, Ethereum}, solver::{self, Solver}, }, - util::{self, conv::u256::U256Ext}, + util, }, - anyhow::Context, chrono::Utc, - num::CheckedDiv, - std::{collections::HashSet, iter, ops::Mul}, + std::{ + collections::{HashMap, HashSet}, + iter, + }, }; /// A quote describing the expected outcome of an order. #[derive(Debug)] pub struct Quote { - /// The amount that can be bought if this was a sell order, or sold if this - /// was a buy order. - pub amount: eth::U256, + pub clearing_prices: HashMap, + pub pre_interactions: Vec, pub interactions: Vec, pub solver: eth::Address, pub gas: Option, /// Which `tx.origin` is required to make the quote simulation pass. pub tx_origin: Option, + pub jit_orders: Vec, } impl Quote { - fn new(eth: &Ethereum, order: &Order, solution: competition::Solution) -> Result { - let sell_price = solution - .clearing_price(order.tokens.sell) - .ok_or(QuotingFailed::ClearingSellMissing)? - .to_big_rational(); - let buy_price = solution - .clearing_price(order.tokens.buy) - .ok_or(QuotingFailed::ClearingBuyMissing)? - .to_big_rational(); - let order_amount = order.amount.0.to_big_rational(); - - let amount = match order.side { - order::Side::Sell => order_amount - .mul(sell_price) - .checked_div(&buy_price) - .context("div by zero: buy price")?, - order::Side::Buy => order_amount - .mul(&buy_price) - .checked_div(&sell_price) - .context("div by zero: sell price")?, - }; - + fn new(eth: &Ethereum, solution: competition::Solution) -> Result { Ok(Self { - amount: eth::U256::from_big_rational(&amount)?, + clearing_prices: solution + .clearing_prices() + .into_iter() + .map(|(token, amount)| (token.into(), amount)) + .collect(), + pre_interactions: solution.pre_interactions().to_vec(), interactions: solution .interactions() .iter() @@ -70,6 +55,14 @@ impl Quote { solver: solution.solver().address(), gas: solution.gas(), tx_origin: *solution.solver().quote_tx_origin(), + jit_orders: solution + .trades() + .iter() + .filter_map(|trade| match trade { + solution::Trade::Jit(jit) => Some(jit.clone()), + _ => None, + }) + .collect(), }) } } @@ -110,7 +103,6 @@ impl Order { let solutions = solver.solve(&auction, &liquidity).await?; Quote::new( eth, - self, // TODO(#1468): choose the best solution in the future, but for now just pick the // first solution solutions diff --git a/crates/driver/src/infra/api/routes/quote/dto/quote.rs b/crates/driver/src/infra/api/routes/quote/dto/quote.rs index 7d7233a5cf..6cc519855e 100644 --- a/crates/driver/src/infra/api/routes/quote/dto/quote.rs +++ b/crates/driver/src/infra/api/routes/quote/dto/quote.rs @@ -1,28 +1,27 @@ use { crate::{ - domain::{eth, quote}, + domain::{self, competition::solution::encoding::codec, eth, quote}, util::serialize, }, + model::{ + order::{BuyTokenDestination, SellTokenSource}, + signature::SigningScheme, + }, serde::Serialize, serde_with::serde_as, + std::collections::HashMap, }; impl Quote { - pub fn new(quote: "e::Quote) -> Self { + pub fn new(quote: quote::Quote) -> Self { Self { - amount: quote.amount, - interactions: quote - .interactions - .iter() - .map(|interaction| Interaction { - target: interaction.target.into(), - value: interaction.value.into(), - call_data: interaction.call_data.clone().into(), - }) - .collect(), + clearing_prices: quote.clearing_prices, + pre_interactions: quote.pre_interactions.into_iter().map(Into::into).collect(), + interactions: quote.interactions.into_iter().map(Into::into).collect(), solver: quote.solver.0, gas: quote.gas.map(|gas| gas.0.as_u64()), tx_origin: quote.tx_origin.map(|addr| addr.0), + jit_orders: quote.jit_orders.into_iter().map(Into::into).collect(), } } } @@ -31,14 +30,16 @@ impl Quote { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct Quote { - #[serde_as(as = "serialize::U256")] - amount: eth::U256, + #[serde_as(as = "HashMap<_, serialize::U256>")] + clearing_prices: HashMap, + pre_interactions: Vec, interactions: Vec, solver: eth::H160, #[serde(skip_serializing_if = "Option::is_none")] gas: Option, #[serde(skip_serializing_if = "Option::is_none")] tx_origin: Option, + jit_orders: Vec, } #[serde_as] @@ -51,3 +52,75 @@ struct Interaction { #[serde_as(as = "serialize::Hex")] call_data: Vec, } + +impl From for Interaction { + fn from(interaction: eth::Interaction) -> Self { + Self { + target: interaction.target.into(), + value: interaction.value.into(), + call_data: interaction.call_data.into(), + } + } +} + +#[serde_as] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct JitOrder { + buy_token: eth::H160, + sell_token: eth::H160, + #[serde_as(as = "serialize::U256")] + sell_amount: eth::U256, + #[serde_as(as = "serialize::U256")] + buy_amount: eth::U256, + #[serde_as(as = "serialize::U256")] + executed_amount: eth::U256, + receiver: eth::H160, + partially_fillable: bool, + valid_to: u32, + #[serde_as(as = "serialize::Hex")] + app_data: [u8; 32], + side: Side, + sell_token_source: SellTokenSource, + buy_token_destination: BuyTokenDestination, + #[serde_as(as = "serialize::Hex")] + signature: Vec, + signing_scheme: SigningScheme, +} + +impl From for JitOrder { + fn from(jit: domain::competition::solution::trade::Jit) -> Self { + Self { + sell_token: jit.order().sell.token.into(), + buy_token: jit.order().buy.token.into(), + sell_amount: jit.order().sell.amount.into(), + buy_amount: jit.order().buy.amount.into(), + executed_amount: jit.executed().into(), + receiver: jit.order().receiver.into(), + partially_fillable: jit.order().partially_fillable, + valid_to: jit.order().valid_to.into(), + app_data: jit.order().app_data.into(), + side: jit.order().side.into(), + sell_token_source: jit.order().sell_token_balance.into(), + buy_token_destination: jit.order().buy_token_balance.into(), + signature: codec::signature(&jit.order().signature).into(), + signing_scheme: jit.order().signature.scheme.to_boundary_scheme(), + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +enum Side { + Sell, + Buy, +} + +impl From for Side { + fn from(side: domain::competition::order::Side) -> Self { + match side { + domain::competition::order::Side::Sell => Side::Sell, + domain::competition::order::Side::Buy => Side::Buy, + } + } +} diff --git a/crates/driver/src/infra/api/routes/quote/mod.rs b/crates/driver/src/infra/api/routes/quote/mod.rs index 09e30fdfd7..c2fe76d824 100644 --- a/crates/driver/src/infra/api/routes/quote/mod.rs +++ b/crates/driver/src/infra/api/routes/quote/mod.rs @@ -33,7 +33,7 @@ async fn route( ) .await; observe::quoted(state.solver().name(), &order, "e); - Ok(axum::response::Json(dto::Quote::new("e?))) + Ok(axum::response::Json(dto::Quote::new(quote?))) }; handle_request diff --git a/crates/driver/src/infra/solver/dto/solution.rs b/crates/driver/src/infra/solver/dto/solution.rs index cab042285d..3a63a9f54d 100644 --- a/crates/driver/src/infra/solver/dto/solution.rs +++ b/crates/driver/src/infra/solver/dto/solution.rs @@ -73,6 +73,7 @@ impl Solutions { token: jit.order.buy_token.into(), }, receiver: jit.order.receiver.into(), + partially_fillable: jit.order.partially_fillable, valid_to: jit.order.valid_to.into(), app_data: jit.order.app_data.into(), side: match jit.order.kind { @@ -286,6 +287,8 @@ struct JitOrder { sell_amount: eth::U256, #[serde_as(as = "serialize::U256")] buy_amount: eth::U256, + #[serde(default)] + partially_fillable: bool, valid_to: u32, #[serde_as(as = "serialize::Hex")] app_data: [u8; order::APP_DATA_LEN], @@ -312,7 +315,7 @@ impl JitOrder { Kind::Sell => OrderKind::Sell, Kind::Buy => OrderKind::Buy, }, - partially_fillable: false, + partially_fillable: self.partially_fillable, sell_token_balance: match self.sell_token_balance { SellTokenBalance::Erc20 => SellTokenSource::Erc20, SellTokenBalance::Internal => SellTokenSource::Internal, diff --git a/crates/driver/src/tests/cases/quote.rs b/crates/driver/src/tests/cases/quote.rs index 1e4f3b8b40..872b2f6bec 100644 --- a/crates/driver/src/tests/cases/quote.rs +++ b/crates/driver/src/tests/cases/quote.rs @@ -2,7 +2,7 @@ use crate::{ domain::competition::order, tests::{ self, - setup::{ab_order, ab_pool, ab_solution}, + setup::{self, ab_order, ab_pool, ab_solution}, }, }; @@ -28,3 +28,39 @@ async fn matrix() { } } } + +#[tokio::test] +#[ignore] +async fn with_jit_order() { + let side = order::Side::Sell; + let kind = order::Kind::Limit; + let jit_order = setup::JitOrder { + order: ab_order() + .kind(order::Kind::Limit) + .side(side) + .kind(kind) + .pre_interaction(setup::blockchain::Interaction { + address: ab_order().owner, + calldata: std::iter::repeat(0xab).take(32).collect(), + inputs: Default::default(), + outputs: Default::default(), + internalize: false, + }) + .no_surplus(), + }; + + let test = tests::setup() + .name(format!("{side:?} {kind:?}")) + .pool(ab_pool()) + .jit_order(jit_order) + .order(ab_order().side(side).kind(kind).no_surplus()) + .solution(ab_solution()) + .quote() + .done() + .await; + + let quote = test.quote().await; + + // Check whether the returned data aligns with the expected. + quote.ok().amount().interactions().jit_order(); +} diff --git a/crates/driver/src/tests/setup/blockchain.rs b/crates/driver/src/tests/setup/blockchain.rs index f0661fb5e1..9c99cb3c8c 100644 --- a/crates/driver/src/tests/setup/blockchain.rs +++ b/crates/driver/src/tests/setup/blockchain.rs @@ -41,7 +41,7 @@ pub struct Blockchain { pub pairs: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Interaction { pub address: ethcontract::H160, pub calldata: Vec, diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index c418f08a26..171ebc6261 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -28,7 +28,7 @@ use { ETH_ORDER_AMOUNT, }, hex_address, - setup::blockchain::{Blockchain, Trade}, + setup::blockchain::{Blockchain, Interaction, Trade}, }, }, app_data::AppDataHash, @@ -48,7 +48,7 @@ use { }, }; -mod blockchain; +pub mod blockchain; mod driver; pub mod fee; mod solver; @@ -139,6 +139,7 @@ pub struct Order { pub buy_token_destination: BuyTokenDestination, pub app_data: AppDataHash, pub quote: Option, + pub pre_interactions: Vec, } impl Order { @@ -274,6 +275,11 @@ impl Order { } } + pub fn pre_interaction(mut self, interaction: Interaction) -> Self { + self.pre_interactions.push(interaction); + self + } + fn surplus_fee(&self) -> eth::U256 { match self.kind { order::Kind::Limit => self.solver_fee.unwrap_or_default(), @@ -317,6 +323,7 @@ impl Default for Order { buy_token_destination: Default::default(), app_data: Default::default(), quote: Default::default(), + pre_interactions: Default::default(), } } } @@ -1077,6 +1084,7 @@ impl Test { trades: &self.trades, status, body, + blockchain: &self.blockchain, } } @@ -1407,6 +1415,7 @@ pub struct Quote<'a> { trades: &'a [Trade], status: StatusCode, body: String, + blockchain: &'a Blockchain, } impl<'a> Quote<'a> { @@ -1416,6 +1425,7 @@ impl<'a> Quote<'a> { QuoteOk { trades: self.trades, body: self.body, + blockchain: self.blockchain, } } } @@ -1423,35 +1433,64 @@ impl<'a> Quote<'a> { pub struct QuoteOk<'a> { trades: &'a [Trade], body: String, + blockchain: &'a Blockchain, } impl QuoteOk<'_> { /// Check that the quote returns the expected amount of tokens. This is /// based on the state of the blockchain and the test setup. pub fn amount(self) -> Self { - assert_eq!(self.trades.len(), 1); - let quoted_order = match &self.trades[0] { - Trade::Fulfillment(fulfillment) => &fulfillment.quoted_order, - Trade::Jit(jit) => &jit.quoted_order, - }; + let quoted_orders = self + .trades + .iter() + .filter_map(|trade| match trade { + Trade::Fulfillment(fulfillment) => Some(&fulfillment.quoted_order), + Trade::Jit(_) => None, + }) + .collect::>(); + assert_eq!(quoted_orders.len(), 1); + + let quoted_order = quoted_orders[0]; + let sell_token = self.blockchain.get_token(quoted_order.order.sell_token); + let buy_token = self.blockchain.get_token(quoted_order.order.buy_token); + let result: serde_json::Value = serde_json::from_str(&self.body).unwrap(); - let amount = result.get("amount").unwrap().as_str().unwrap().to_owned(); + let clearing_prices = result + .get("clearingPrices") + .unwrap() + .as_object() + .unwrap() + .into_iter() + .map(|(token, price)| (H160::from_str(token).unwrap(), price.as_str().unwrap())) + .collect::>(); + + let amount = match quoted_order.order.side { + order::Side::Buy => clearing_prices.get(&buy_token).unwrap(), + order::Side::Sell => clearing_prices.get(&sell_token).unwrap(), + }; + let expected = match quoted_order.order.side { order::Side::Buy => (quoted_order.sell - quoted_order.order.surplus_fee()).to_string(), order::Side::Sell => quoted_order.buy.to_string(), }; - assert_eq!(amount, expected); + assert_eq!(amount, &expected); self } /// Check that the quote returns the expected interactions. This is /// based on the state of the blockchain and the test setup. pub fn interactions(self) -> Self { - assert_eq!(self.trades.len(), 1); - let interactions = match &self.trades[0] { - Trade::Fulfillment(fulfillment) => fulfillment.interactions.as_slice(), - Trade::Jit(jit) => jit.interactions.as_slice(), - }; + let interactions = self + .trades + .iter() + .filter_map(|trade| match trade { + Trade::Fulfillment(fulfillment) => Some(fulfillment.interactions.as_slice()), + Trade::Jit(_) => None, + }) + .collect::>(); + assert_eq!(interactions.len(), 1); + + let interactions = interactions[0]; let result: serde_json::Value = serde_json::from_str(&self.body).unwrap(); let result_interactions = result .get("interactions") @@ -1470,6 +1509,54 @@ impl QuoteOk<'_> { } self } + + /// Checks that quote returns the expected jir orders. This is based on the + /// test setup. + pub fn jit_order(self) -> Self { + let expected_jit_orders = self + .trades + .iter() + .filter_map(|trade| match trade { + Trade::Fulfillment(_) => None, + Trade::Jit(jit) => Some(jit), + }) + .collect::>(); + assert_eq!(expected_jit_orders.len(), 1); + + let result: serde_json::Value = serde_json::from_str(&self.body).unwrap(); + let jit_orders = result.get("jitOrders").unwrap().as_array().unwrap(); + assert_eq!(jit_orders.len(), 1); + let expected = expected_jit_orders[0]; + let result_jit_order = jit_orders[0].as_object().unwrap(); + let app_data = result_jit_order.get("appData").unwrap().as_str().unwrap(); + assert_eq!( + app_data, + format!("0x{}", hex::encode(expected.quoted_order.order.app_data.0)) + ); + + let result_pre_interactions = result + .get("preInteractions") + .unwrap() + .as_array() + .unwrap() + .to_owned(); + assert_eq!( + result_pre_interactions.len(), + expected.quoted_order.order.pre_interactions.len() + ); + for (interaction, expected) in result_pre_interactions + .iter() + .zip(&expected.quoted_order.order.pre_interactions) + { + let target = interaction.get("target").unwrap().as_str().unwrap(); + let value = interaction.get("value").unwrap().as_str().unwrap(); + let calldata = interaction.get("callData").unwrap().as_str().unwrap(); + assert_eq!(target, format!("0x{}", hex::encode(expected.address))); + assert_eq!(value, "0"); + assert_eq!(calldata, format!("0x{}", hex::encode(&expected.calldata))); + } + self + } } #[serde_as] diff --git a/crates/driver/src/tests/setup/solver.rs b/crates/driver/src/tests/setup/solver.rs index 777e6416d2..ab45bf1a94 100644 --- a/crates/driver/src/tests/setup/solver.rs +++ b/crates/driver/src/tests/setup/solver.rs @@ -167,6 +167,7 @@ impl Solver { orders_json.push(order); } for (i, solution) in config.solutions.iter().enumerate() { + let mut pre_interactions_json = Vec::new(); let mut interactions_json = Vec::new(); let mut prices_json = HashMap::new(); let mut trades_json = Vec::new(); @@ -250,6 +251,31 @@ impl Solver { } } Trade::Jit(jit) => { + pre_interactions_json + .extend(jit.quoted_order.order.pre_interactions.iter().map( + |interaction| { + json!({ + "kind": "custom", + "internalize": interaction.internalize, + "target": hex_address(interaction.address), + "value": "0", + "callData": format!("0x{}", hex::encode(&interaction.calldata)), + "allowances": [], + "inputs": interaction.inputs.iter().map(|input| { + json!({ + "token": hex_address(input.token.into()), + "amount": input.amount.to_string(), + }) + }).collect_vec(), + "outputs": interaction.outputs.iter().map(|output| { + json!({ + "token": hex_address(output.token.into()), + "amount": output.amount.to_string(), + }) + }).collect_vec(), + }) + }, + )); interactions_json.extend(jit.interactions.iter().map(|interaction| { json!({ "kind": "custom", @@ -323,7 +349,7 @@ impl Solver { }, "sellTokenBalance": jit.quoted_order.order.sell_token_source, "buyTokenBalance": jit.quoted_order.order.buy_token_destination, - "signature": if config.quote { "0x".to_string() } else { format!("0x{}", hex::encode(jit.quoted_order.order_signature_with_private_key(config.blockchain, &config.private_key))) }, + "signature": format!("0x{}", hex::encode(jit.quoted_order.order_signature_with_private_key(config.blockchain, &config.private_key))), "signingScheme": if config.quote { "eip1271" } else { "eip712" }, }); trades_json.push(json!({ @@ -341,6 +367,7 @@ impl Solver { "prices": prices_json, "trades": trades_json, "interactions": interactions_json, + "preInteractions": pre_interactions_json, })); } diff --git a/crates/e2e/src/setup/colocation.rs b/crates/e2e/src/setup/colocation.rs index c37578dcc5..da59d6c8f2 100644 --- a/crates/e2e/src/setup/colocation.rs +++ b/crates/e2e/src/setup/colocation.rs @@ -97,6 +97,7 @@ pub fn start_driver( contracts: &Contracts, solvers: Vec, liquidity: LiquidityProvider, + quote_using_limit_orders: bool, ) -> JoinHandle<()> { let base_tokens: HashSet<_> = solvers .iter() @@ -122,6 +123,7 @@ endpoint = "{endpoint}" relative-slippage = "0.1" account = "{account}" merge-solutions = {merge_solutions} +quote-using-limit-orders = {quote_using_limit_orders} "# ) }, diff --git a/crates/e2e/src/setup/services.rs b/crates/e2e/src/setup/services.rs index 116ea605c5..6e16bb80fa 100644 --- a/crates/e2e/src/setup/services.rs +++ b/crates/e2e/src/setup/services.rs @@ -210,6 +210,7 @@ impl<'a> Services<'a> { .await, ], colocation::LiquidityProvider::UniswapV2, + false, ); self.start_autopilot( None, @@ -303,6 +304,7 @@ impl<'a> Services<'a> { self.contracts, solvers, colocation::LiquidityProvider::UniswapV2, + false, ); self.start_autopilot(Some(Duration::from_secs(11)), autopilot_args) diff --git a/crates/e2e/tests/e2e/buffers.rs b/crates/e2e/tests/e2e/buffers.rs index fe388a4be5..398b13a547 100644 --- a/crates/e2e/tests/e2e/buffers.rs +++ b/crates/e2e/tests/e2e/buffers.rs @@ -54,6 +54,7 @@ async fn onchain_settlement_without_liquidity(web3: Web3) { .await, ], colocation::LiquidityProvider::UniswapV2, + false, ); let services = Services::new(&onchain).await; services diff --git a/crates/e2e/tests/e2e/cow_amm.rs b/crates/e2e/tests/e2e/cow_amm.rs index d116659ece..8c419bcf1c 100644 --- a/crates/e2e/tests/e2e/cow_amm.rs +++ b/crates/e2e/tests/e2e/cow_amm.rs @@ -1,6 +1,7 @@ use { app_data::AppDataHash, contracts::ERC20, + driver::domain::eth::NonZeroU256, e2e::{ nodes::forked_node::ForkedNodeApi, setup::{colocation::SolverEngine, mock::Mock, *}, @@ -9,7 +10,7 @@ use { }, ethcontract::{web3::ethabi::Token, BlockId, BlockNumber, H160, U256}, model::{ - order::{OrderClass, OrderCreation, OrderData, OrderKind}, + order::{OrderClass, OrderCreation, OrderData, OrderKind, OrderUid}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, signature::{hashed_eip712_message, EcdsaSigningScheme}, }, @@ -152,6 +153,7 @@ async fn cow_amm_jit(web3: Web3) { }, ], colocation::LiquidityProvider::UniswapV2, + false, ); let services = Services::new(&onchain).await; services @@ -474,6 +476,7 @@ async fn cow_amm_driver_support(web3: Web3) { }, ], colocation::LiquidityProvider::UniswapV2, + false, ); let services = Services::new(&onchain).await; @@ -605,3 +608,391 @@ async fn cow_amm_driver_support(web3: Web3) { .await .unwrap(); } + +#[tokio::test] +#[ignore] +async fn local_node_cow_amm_opposite_direction() { + run_test(cow_amm_opposite_direction).await; +} + +/// Tests that only CoW AMM liquidity can be used to fulfill the order. +async fn cow_amm_opposite_direction(web3: Web3) { + let mut onchain = OnchainComponents::deploy(web3.clone()).await; + + let [solver] = onchain.make_solvers(to_wei(100)).await; + let [bob, cow_amm_owner] = onchain.make_accounts(to_wei(1000)).await; + + let [dai] = onchain + .deploy_tokens_with_weth_uni_v2_pools(to_wei(300_000), to_wei(100)) + .await; + + // No need to fund the buffers since we're testing the CoW AMM directly filling + // the user order. + + // Set up the CoW AMM as before + let oracle = contracts::CowAmmUniswapV2PriceOracle::builder(&web3) + .deploy() + .await + .unwrap(); + + let cow_amm_factory = contracts::CowAmmConstantProductFactory::builder( + &web3, + onchain.contracts().gp_settlement.address(), + ) + .deploy() + .await + .unwrap(); + + // Fund the CoW AMM owner with DAI and WETH and approve the factory to transfer + // them + dai.mint(cow_amm_owner.address(), to_wei(2_000)).await; + tx!( + cow_amm_owner.account(), + dai.approve(cow_amm_factory.address(), to_wei(2_000)) + ); + + tx_value!( + cow_amm_owner.account(), + to_wei(1), + onchain.contracts().weth.deposit() + ); + tx!( + cow_amm_owner.account(), + onchain + .contracts() + .weth + .approve(cow_amm_factory.address(), to_wei(1)) + ); + + tx_value!( + solver.account(), + to_wei(1), + onchain.contracts().weth.deposit() + ); + + let pair = onchain + .contracts() + .uniswap_v2_factory + .get_pair(onchain.contracts().weth.address(), dai.address()) + .call() + .await + .expect("failed to get Uniswap V2 pair"); + + let cow_amm_address = cow_amm_factory + .amm_deterministic_address( + cow_amm_owner.address(), + dai.address(), + onchain.contracts().weth.address(), + ) + .call() + .await + .unwrap(); + + let oracle_data: Vec<_> = std::iter::repeat(0u8) + .take(12) // pad with 12 zeros to end up with 32 bytes + .chain(pair.as_bytes().to_vec()) + .collect(); + const APP_DATA: [u8; 32] = [12u8; 32]; + + // Create the CoW AMM + cow_amm_factory + .create( + dai.address(), + to_wei(2_000), + onchain.contracts().weth.address(), + to_wei(1), + 0.into(), // min traded token + oracle.address(), + ethcontract::Bytes(oracle_data.clone()), + ethcontract::Bytes(APP_DATA), + ) + .from(cow_amm_owner.account().clone()) + .send() + .await + .unwrap(); + let cow_amm = contracts::CowAmm::at(&web3, cow_amm_address); + + // Start system with the mocked solver. Baseline is still required for the + // native price estimation. + let mock_solver = Mock::default(); + colocation::start_driver( + onchain.contracts(), + vec![ + colocation::start_baseline_solver( + "test_solver".into(), + solver.clone(), + onchain.contracts().weth.address(), + vec![], + 1, + true, + ) + .await, + SolverEngine { + name: "mock_solver".into(), + account: solver.clone(), + endpoint: mock_solver.url.clone(), + base_tokens: vec![], + merge_solutions: true, + }, + ], + colocation::LiquidityProvider::UniswapV2, + true, + ); + let services = Services::new(&onchain).await; + services + .start_autopilot( + None, + vec![ + "--drivers=mock_solver|http://localhost:11088/mock_solver".to_string(), + "--price-estimation-drivers=mock_solver|http://localhost:11088/mock_solver" + .to_string(), + ], + ) + .await; + services + .start_api(vec![ + "--price-estimation-drivers=mock_solver|http://localhost:11088/mock_solver".to_string(), + ]) + .await; + + // Get the current block timestamp + let block = web3 + .eth() + .block(BlockId::Number(BlockNumber::Latest)) + .await + .unwrap() + .unwrap(); + let valid_to = block.timestamp.as_u32() + 300; + let executed_amount = to_wei(230); + + // CoW AMM order remains the same (selling WETH for DAI) + let cow_amm_order = OrderData { + sell_token: onchain.contracts().weth.address(), + buy_token: dai.address(), + receiver: None, + sell_amount: U256::exp10(17), // 0.1 WETH + buy_amount: executed_amount, // 230 DAI + valid_to, + app_data: AppDataHash(APP_DATA), + fee_amount: 0.into(), + kind: OrderKind::Sell, + partially_fillable: false, + sell_token_balance: Default::default(), + buy_token_balance: Default::default(), + }; + + // Create the signature for the CoW AMM order + let signature_data = ethcontract::web3::ethabi::encode(&[ + Token::Tuple(vec![ + Token::Address(cow_amm_order.sell_token), + Token::Address(cow_amm_order.buy_token), + Token::Address(cow_amm_order.receiver.unwrap_or_default()), + Token::Uint(cow_amm_order.sell_amount), + Token::Uint(cow_amm_order.buy_amount), + Token::Uint(cow_amm_order.valid_to.into()), + Token::FixedBytes(cow_amm_order.app_data.0.to_vec()), + Token::Uint(cow_amm_order.fee_amount), + Token::FixedBytes( + hex::decode("f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee346775") + .unwrap(), + ), // sell order + Token::Bool(cow_amm_order.partially_fillable), + Token::FixedBytes( + hex::decode("5a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc9") + .unwrap(), + ), // sell_token_source == erc20 + Token::FixedBytes( + hex::decode("5a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc9") + .unwrap(), + ), // buy_token_destination == erc20 + ]), + Token::Tuple(vec![ + Token::Uint(0.into()), // min_traded_token + Token::Address(oracle.address()), + Token::Bytes(oracle_data), + Token::FixedBytes(APP_DATA.to_vec()), + ]), + ]); + + // Prepend CoW AMM address to the signature so the settlement contract knows + // which contract this signature refers to. + let signature = cow_amm + .address() + .as_bytes() + .iter() + .cloned() + .chain(signature_data) + .collect::>(); + + // Create the commitment call for the pre-interaction + let cow_amm_commitment = { + let order_hash = cow_amm_order.hash_struct(); + let order_hash = hashed_eip712_message(&onchain.contracts().domain_separator, &order_hash); + let commitment = cow_amm + .commit(ethcontract::Bytes(order_hash)) + .tx + .data + .unwrap(); + Call { + target: cow_amm.address(), + value: 0.into(), + calldata: commitment.0.to_vec(), + } + }; + + // Fund trader "bob" with DAI and approve allowance + dai.mint(bob.address(), to_wei(250)).await; + tx!( + bob.account(), + dai.approve(onchain.contracts().allowance, U256::MAX) + ); + + // Get balances before the trade + let amm_weth_balance_before = onchain + .contracts() + .weth + .balance_of(cow_amm.address()) + .call() + .await + .unwrap(); + let bob_weth_balance_before = onchain + .contracts() + .weth + .balance_of(bob.address()) + .call() + .await + .unwrap(); + + // Compensate a delay between the `CurrentBlockStream` and the actual onchain + // data. + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + // Set the fees appropriately + let fee_cow_amm = U256::exp10(16); // 0.01 WETH + let fee_user = to_wei(1); // 1 DAI + + let mocked_solutions = |order_uid: OrderUid| { + Solution { + id: 1, + prices: HashMap::from([ + (dai.address(), to_wei(1)), // 1 DAI = $1 + (onchain.contracts().weth.address(), to_wei(2300)), // 1 WETH = $2300 + ]), + trades: vec![ + solvers_dto::solution::Trade::Jit(solvers_dto::solution::JitTrade { + order: solvers_dto::solution::JitOrder { + sell_token: cow_amm_order.sell_token, + buy_token: cow_amm_order.buy_token, + receiver: cow_amm_order.receiver.unwrap_or_default(), + sell_amount: cow_amm_order.sell_amount, + buy_amount: cow_amm_order.buy_amount, + valid_to: cow_amm_order.valid_to, + app_data: cow_amm_order.app_data.0, + kind: Kind::Sell, + sell_token_balance: SellTokenBalance::Erc20, + buy_token_balance: BuyTokenBalance::Erc20, + signing_scheme: SigningScheme::Eip1271, + signature: signature.clone(), + }, + executed_amount: cow_amm_order.sell_amount - fee_cow_amm, + fee: Some(fee_cow_amm), + }), + solvers_dto::solution::Trade::Fulfillment(solvers_dto::solution::Fulfillment { + order: order_uid.0, + executed_amount: executed_amount - fee_user, + fee: Some(fee_user), + }), + ], + pre_interactions: vec![cow_amm_commitment.clone()], + interactions: vec![], + post_interactions: vec![], + gas: None, + } + }; + + // Configure the mocked `/quote` solver's solution + let mocked_quote_solution = mocked_solutions(OrderUid([0u8; 56])); + mock_solver.configure_solution(Some(mocked_quote_solution)); + + let quote_request = OrderQuoteRequest { + from: bob.address(), + sell_token: dai.address(), + buy_token: onchain.contracts().weth.address(), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::AfterFee { + value: NonZeroU256::try_from(executed_amount).unwrap(), + }, + }, + ..Default::default() + }; + + // Must align with the mocked_solutions. + let quote_response = services.submit_quote("e_request).await.unwrap(); + assert!(quote_response.verified); + assert_eq!(quote_response.quote.sell_token, dai.address()); + assert_eq!( + quote_response.quote.buy_token, + onchain.contracts().weth.address() + ); + // Ensure the amounts are the same as the solution proposes. + assert_eq!(quote_response.quote.sell_amount, executed_amount); + assert_eq!(quote_response.quote.buy_amount, U256::exp10(17)); + + // Place user order where bob sells DAI to buy WETH (opposite direction) + let user_order = OrderCreation { + sell_token: dai.address(), + sell_amount: executed_amount, // 230 DAI + buy_token: onchain.contracts().weth.address(), + buy_amount: U256::from(90000000000000000u64), // 0.09 WETH to generate some surplus + valid_to: model::time::now_in_epoch_seconds() + 300, + kind: OrderKind::Sell, + ..Default::default() + } + .sign( + EcdsaSigningScheme::Eip712, + &onchain.contracts().domain_separator, + SecretKeyRef::from(&SecretKey::from_slice(bob.private_key()).unwrap()), + ); + let user_order_id = services.create_order(&user_order).await.unwrap(); + + // Configure the mocked `/solve` solver's solution + let mocked_solve_solution = mocked_solutions(user_order_id); + mock_solver.configure_solution(Some(mocked_solve_solution.clone())); + + // Drive solution + tracing::info!("Waiting for trade."); + onchain.mint_block().await; + wait_for_condition(TIMEOUT, || async { + let amm_weth_balance_after = onchain + .contracts() + .weth + .balance_of(cow_amm.address()) + .call() + .await + .unwrap(); + let bob_weth_balance_after = onchain + .contracts() + .weth + .balance_of(bob.address()) + .call() + .await + .unwrap(); + + let amm_weth_sent = amm_weth_balance_before - amm_weth_balance_after; + let bob_weth_received = bob_weth_balance_after - bob_weth_balance_before; + + // Bob should receive WETH, CoW AMM's WETH balance decreases + bob_weth_received >= user_order.buy_amount && amm_weth_sent == cow_amm_order.sell_amount + }) + .await + .unwrap(); + + // Verify that the trade is indexed + tracing::info!("Waiting for trade to be indexed."); + wait_for_condition(TIMEOUT, || async { + let trades = services.get_trades(&user_order_id).await.ok(); + trades.is_some_and(|trades| !trades.is_empty()) + }) + .await + .unwrap(); +} diff --git a/crates/e2e/tests/e2e/jit_orders.rs b/crates/e2e/tests/e2e/jit_orders.rs index aa955a23e2..a37514e894 100644 --- a/crates/e2e/tests/e2e/jit_orders.rs +++ b/crates/e2e/tests/e2e/jit_orders.rs @@ -76,6 +76,7 @@ async fn single_limit_order_test(web3: Web3) { }, ], colocation::LiquidityProvider::UniswapV2, + false, ); // We start the quoter as the baseline solver, and the mock solver as the one diff --git a/crates/e2e/tests/e2e/limit_orders.rs b/crates/e2e/tests/e2e/limit_orders.rs index e07c827cbb..c1819db63b 100644 --- a/crates/e2e/tests/e2e/limit_orders.rs +++ b/crates/e2e/tests/e2e/limit_orders.rs @@ -358,6 +358,7 @@ async fn two_limit_orders_multiple_winners_test(web3: Web3) { .await, ], colocation::LiquidityProvider::UniswapV2, + false, ); let services = Services::new(&onchain).await; @@ -481,6 +482,7 @@ async fn too_many_limit_orders_test(web3: Web3) { .await, ], colocation::LiquidityProvider::UniswapV2, + false, ); services .start_api(vec![ @@ -559,6 +561,7 @@ async fn limit_does_not_apply_to_in_market_orders_test(web3: Web3) { .await, ], colocation::LiquidityProvider::UniswapV2, + false, ); services .start_api(vec![ diff --git a/crates/e2e/tests/e2e/liquidity.rs b/crates/e2e/tests/e2e/liquidity.rs index a480b0014a..1d10ca104a 100644 --- a/crates/e2e/tests/e2e/liquidity.rs +++ b/crates/e2e/tests/e2e/liquidity.rs @@ -149,6 +149,7 @@ async fn zero_ex_liquidity(web3: Web3) { colocation::LiquidityProvider::ZeroEx { api_port: zeroex_api_port, }, + false, ); services .start_autopilot( diff --git a/crates/e2e/tests/e2e/order_cancellation.rs b/crates/e2e/tests/e2e/order_cancellation.rs index fca96e024b..b92fd0f827 100644 --- a/crates/e2e/tests/e2e/order_cancellation.rs +++ b/crates/e2e/tests/e2e/order_cancellation.rs @@ -61,6 +61,7 @@ async fn order_cancellation(web3: Web3) { .await, ], colocation::LiquidityProvider::UniswapV2, + false, ); services .start_autopilot( diff --git a/crates/e2e/tests/e2e/solver_competition.rs b/crates/e2e/tests/e2e/solver_competition.rs index 785a4488e6..b2428ac1cd 100644 --- a/crates/e2e/tests/e2e/solver_competition.rs +++ b/crates/e2e/tests/e2e/solver_competition.rs @@ -65,6 +65,7 @@ async fn solver_competition(web3: Web3) { .await, ], colocation::LiquidityProvider::UniswapV2, + false, ); let services = Services::new(&onchain).await; @@ -190,6 +191,7 @@ async fn fairness_check(web3: Web3) { .await, ], colocation::LiquidityProvider::UniswapV2, + false, ); let services = Services::new(&onchain).await; diff --git a/crates/solvers/openapi.yml b/crates/solvers/openapi.yml index f156c0ec1a..7c6f971a79 100644 --- a/crates/solvers/openapi.yml +++ b/crates/solvers/openapi.yml @@ -710,6 +710,8 @@ components: $ref: "#/components/schemas/AppData" kind: $ref: "#/components/schemas/OrderKind" + partiallyFillable: + type: boolean sellTokenBalance: $ref: "#/components/schemas/SellTokenBalance" buyTokenBalance: From 49871f27ad91ae7f63954f799ba42fb7ea4b2fe0 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Wed, 4 Dec 2024 08:00:51 +0100 Subject: [PATCH 03/25] [EASY] Delete dead code (#3146) # Description I was curious about our test coverage and noticed that the generated report can be used pretty well to find dead code that the compiler is not able to detect because the functions get exporter from the respective crates. # Changes just deletes dead code & removes some `drive` statements ## How to test no logic change => compiler is enough --- crates/app-data/src/hooks.rs | 4 - .../src/infra/blockchain/authenticator.rs | 85 -------------- crates/autopilot/src/infra/blockchain/mod.rs | 1 - .../autopilot/src/infra/solvers/dto/settle.rs | 21 +--- crates/autopilot/src/util/bytes.rs | 12 -- .../src/domain/competition/order/mod.rs | 7 -- .../src/domain/competition/order/signature.rs | 14 --- .../competition/solution/interaction.rs | 9 -- .../domain/competition/solution/settlement.rs | 5 - .../src/domain/competition/solution/trade.rs | 4 - crates/driver/src/domain/eth/gas.rs | 34 ------ crates/driver/src/infra/blockchain/mod.rs | 8 +- crates/ethrpc/src/extensions.rs | 4 +- crates/model/src/lib.rs | 1 - crates/model/src/order.rs | 21 ---- crates/model/src/quote.rs | 20 ---- crates/model/src/ratio_as_decimal.rs | 110 ------------------ crates/orderbook/src/api/get_native_price.rs | 13 +-- crates/orderbook/src/dto/auction.rs | 2 +- crates/shared/src/code_simulation.rs | 1 - crates/shared/src/external_prices.rs | 57 +-------- crates/shared/src/gas_price_estimation.rs | 2 +- crates/shared/src/http_client.rs | 45 +------ crates/shared/src/http_solver.rs | 12 +- crates/shared/src/interaction.rs | 14 +-- crates/shared/src/lib.rs | 14 --- crates/shared/src/maintenance.rs | 4 - crates/shared/src/price_estimation/mod.rs | 28 ----- crates/shared/src/trade_finding/mod.rs | 21 +--- crates/solver/src/liquidity/mod.rs | 107 ----------------- crates/solver/src/liquidity/slippage.rs | 10 -- crates/solver/src/liquidity_collector.rs | 1 - crates/solver/src/settlement/mod.rs | 26 ----- .../src/settlement/settlement_encoder.rs | 15 --- crates/solvers/src/domain/auction.rs | 4 - crates/solvers/src/domain/eth/mod.rs | 26 ----- .../src/domain/liquidity/limit_order.rs | 10 -- .../src/domain/liquidity/weighted_product.rs | 9 -- crates/solvers/src/domain/order.rs | 17 --- crates/solvers/src/domain/solution.rs | 10 -- crates/solvers/src/infra/blockchain.rs | 13 --- crates/solvers/src/infra/metrics.rs | 4 - crates/solvers/src/infra/mod.rs | 1 - crates/solvers/src/util/bytes.rs | 18 --- crates/solvers/src/util/conv.rs | 18 +-- crates/solvers/src/util/mod.rs | 1 - crates/solvers/src/util/serialize/hex.rs | 56 --------- crates/solvers/src/util/serialize/mod.rs | 1 - crates/solvers/src/util/serialize/never.rs | 25 ---- crates/solvers/src/util/serialize/str.rs | 10 -- crates/solvers/src/util/serialize/u256.rs | 13 +-- crates/solvers/src/util/url.rs | 14 --- 52 files changed, 18 insertions(+), 964 deletions(-) delete mode 100644 crates/autopilot/src/infra/blockchain/authenticator.rs delete mode 100644 crates/model/src/ratio_as_decimal.rs delete mode 100644 crates/solvers/src/infra/blockchain.rs delete mode 100644 crates/solvers/src/util/serialize/hex.rs delete mode 100644 crates/solvers/src/util/serialize/never.rs delete mode 100644 crates/solvers/src/util/serialize/str.rs diff --git a/crates/app-data/src/hooks.rs b/crates/app-data/src/hooks.rs index 6651d6c339..4ebf6dc66f 100644 --- a/crates/app-data/src/hooks.rs +++ b/crates/app-data/src/hooks.rs @@ -20,10 +20,6 @@ pub struct Hooks { } impl Hooks { - pub fn is_empty(&self) -> bool { - self.pre.is_empty() && self.post.is_empty() - } - pub fn gas_limit(&self) -> u64 { std::iter::empty() .chain(&self.pre) diff --git a/crates/autopilot/src/infra/blockchain/authenticator.rs b/crates/autopilot/src/infra/blockchain/authenticator.rs deleted file mode 100644 index bf0d72faa1..0000000000 --- a/crates/autopilot/src/infra/blockchain/authenticator.rs +++ /dev/null @@ -1,85 +0,0 @@ -use { - crate::{ - domain::{self, eth}, - infra::blockchain::contracts::{deployment_address, Contracts}, - }, - chain::Chain, - ethcontract::{dyns::DynWeb3, GasPrice}, -}; - -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub struct Manager { - /// The authenticator contract that decides which solver is allowed to - /// submit settlements. - authenticator: contracts::GPv2AllowListAuthentication, - /// The safe module that is used to provide special role to EOA. - authenticator_role: contracts::Roles, - /// The EOA that is allowed to remove solvers. - authenticator_eoa: ethcontract::Account, -} - -impl Manager { - /// Creates an authenticator which can remove solvers from the allow-list - pub async fn new( - web3: DynWeb3, - chain: &Chain, - contracts: Contracts, - authenticator_pk: eth::H256, - ) -> Self { - let authenticator_role = contracts::Roles::at( - &web3, - deployment_address(contracts::Roles::raw_contract(), chain).expect("roles address"), - ); - - Self { - authenticator: contracts.authenticator().clone(), - authenticator_role, - authenticator_eoa: ethcontract::Account::Offline( - ethcontract::PrivateKey::from_raw(authenticator_pk.0).unwrap(), - None, - ), - } - } - - /// Fire and forget: Removes solver from the allow-list in the authenticator - /// contract. This solver will no longer be able to settle. - #[allow(dead_code)] - fn remove_solver(&self, solver: domain::eth::Address) { - let calldata = self - .authenticator - .methods() - .remove_solver(solver.into()) - .tx - .data - .expect("missing calldata"); - let authenticator_eoa = self.authenticator_eoa.clone(); - let authenticator_address = self.authenticator.address(); - let authenticator_role = self.authenticator_role.clone(); - tokio::task::spawn(async move { - // This value comes from the TX posted in the issue: https://github.com/cowprotocol/services/issues/2667 - let mut byte_array = [0u8; 32]; - byte_array[31] = 1; - authenticator_role - .methods() - .exec_transaction_with_role( - authenticator_address, - 0.into(), - ethcontract::Bytes(calldata.0), - 0, - ethcontract::Bytes(byte_array), - true, - ) - .from(authenticator_eoa) - .gas_price(GasPrice::Eip1559 { - // These are arbitrary high numbers that should be enough for a tx to be settled - // anytime. - max_fee_per_gas: 1000.into(), - max_priority_fee_per_gas: 5.into(), - }) - .send() - .await - .inspect_err(|err| tracing::error!(?solver, ?err, "failed to remove the solver")) - }); - } -} diff --git a/crates/autopilot/src/infra/blockchain/mod.rs b/crates/autopilot/src/infra/blockchain/mod.rs index f030cdf7eb..6b8f3234cc 100644 --- a/crates/autopilot/src/infra/blockchain/mod.rs +++ b/crates/autopilot/src/infra/blockchain/mod.rs @@ -10,7 +10,6 @@ use { url::Url, }; -pub mod authenticator; pub mod contracts; /// An Ethereum RPC connection. diff --git a/crates/autopilot/src/infra/solvers/dto/settle.rs b/crates/autopilot/src/infra/solvers/dto/settle.rs index ef7f34465a..3abdf15dbe 100644 --- a/crates/autopilot/src/infra/solvers/dto/settle.rs +++ b/crates/autopilot/src/infra/solvers/dto/settle.rs @@ -1,6 +1,5 @@ use { - primitive_types::H256, - serde::{Deserialize, Serialize}, + serde::Serialize, serde_with::{serde_as, skip_serializing_none}, }; @@ -18,21 +17,3 @@ pub struct Request { #[serde_as(as = "Option")] pub auction_id: Option, } - -#[serde_as] -#[derive(Clone, Debug, Default, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Response { - pub calldata: Calldata, - pub tx_hash: H256, -} - -#[serde_as] -#[derive(Clone, Debug, Default, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Calldata { - #[serde(with = "bytes_hex")] - pub internalized: Vec, - #[serde(with = "bytes_hex")] - pub uninternalized: Vec, -} diff --git a/crates/autopilot/src/util/bytes.rs b/crates/autopilot/src/util/bytes.rs index 1ff531c545..bf55ab60e5 100644 --- a/crates/autopilot/src/util/bytes.rs +++ b/crates/autopilot/src/util/bytes.rs @@ -16,15 +16,3 @@ impl From for Bytes { Self(value) } } - -impl From>> for Vec { - fn from(value: Bytes>) -> Self { - value.0 - } -} - -impl From> for [u8; N] { - fn from(value: Bytes<[u8; N]>) -> Self { - value.0 - } -} diff --git a/crates/driver/src/domain/competition/order/mod.rs b/crates/driver/src/domain/competition/order/mod.rs index 0088d36f07..5b82b1d758 100644 --- a/crates/driver/src/domain/competition/order/mod.rs +++ b/crates/driver/src/domain/competition/order/mod.rs @@ -406,13 +406,6 @@ impl Jit { } } - /// Returns the signed fee of the order. You can't set this field in - /// the API so it's enforced to be 0. This function only exists to - /// not have magic values scattered everywhere. - pub fn fee(&self) -> SellAmount { - SellAmount(0.into()) - } - /// Returns the signed partially fillable property of the order. You can't /// set this field in the API so it's enforced to be fill-or-kill. This /// function only exists to not have magic values scattered everywhere. diff --git a/crates/driver/src/domain/competition/order/signature.rs b/crates/driver/src/domain/competition/order/signature.rs index dd51692a45..2964fdc8e3 100644 --- a/crates/driver/src/domain/competition/order/signature.rs +++ b/crates/driver/src/domain/competition/order/signature.rs @@ -52,17 +52,3 @@ impl Scheme { } } } - -pub fn domain_separator( - chain_id: chain::Id, - verifying_contract: eth::ContractAddress, -) -> eth::DomainSeparator { - eth::DomainSeparator::new(ð::DomainFields { - type_hash: - b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", - name: b"Gnosis Protocol", - version: b"v2", - chain_id, - verifying_contract, - }) -} diff --git a/crates/driver/src/domain/competition/solution/interaction.rs b/crates/driver/src/domain/competition/solution/interaction.rs index 83fbb82af4..19e614c085 100644 --- a/crates/driver/src/domain/competition/solution/interaction.rs +++ b/crates/driver/src/domain/competition/solution/interaction.rs @@ -30,15 +30,6 @@ impl Interaction { } } - /// The assets output by this interaction. These assets are sent into the - /// settlement contract when the interaction executes. - pub fn outputs(&self) -> Vec { - match self { - Interaction::Custom(custom) => custom.outputs.clone(), - Interaction::Liquidity(liquidity) => vec![liquidity.output], - } - } - /// Returns the ERC20 approvals required for executing this interaction /// onchain. pub fn allowances(&self) -> Vec { diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index 0ea089685b..c3fc3e5b28 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -252,11 +252,6 @@ impl Settlement { self.solution.id() } - /// Address of the solver which generated this settlement. - pub fn solver(&self) -> eth::Address { - self.solution.solver().address() - } - /// The settled user orders with their in/out amounts. pub fn orders(&self) -> HashMap { let log_err = |trade: &Trade, err: error::Math, kind: &str| -> eth::TokenAmount { diff --git a/crates/driver/src/domain/competition/solution/trade.rs b/crates/driver/src/domain/competition/solution/trade.rs index 299b483502..5c24d66bf5 100644 --- a/crates/driver/src/domain/competition/solution/trade.rs +++ b/crates/driver/src/domain/competition/solution/trade.rs @@ -182,10 +182,6 @@ impl Fulfillment { &self.order } - pub fn side(&self) -> Side { - self.order.side - } - pub fn executed(&self) -> order::TargetAmount { self.executed } diff --git a/crates/driver/src/domain/eth/gas.rs b/crates/driver/src/domain/eth/gas.rs index 2ec2c5de36..e7c4fd67d9 100644 --- a/crates/driver/src/domain/eth/gas.rs +++ b/crates/driver/src/domain/eth/gas.rs @@ -1,6 +1,5 @@ use { super::{Ether, U256}, - bigdecimal::Zero, derive_more::{Display, From, Into}, std::{ops, ops::Add}, }; @@ -26,16 +25,6 @@ impl Add for Gas { } } -impl Zero for Gas { - fn zero() -> Self { - Self(U256::zero()) - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} - /// An EIP-1559 gas price estimate. /// /// https://eips.ethereum.org/EIPS/eip-1559#specification @@ -118,11 +107,6 @@ impl From for GasPrice { pub struct FeePerGas(pub Ether); impl FeePerGas { - /// Subtracts the given fee from this fee, saturating at zero. - pub fn saturating_sub(self, rhs: Self) -> Self { - self.0 .0.saturating_sub(rhs.0 .0).into() - } - /// Multiplies this fee by the given floating point number, rounding up. fn mul_ceil(self, rhs: f64) -> Self { U256::from_f64_lossy((self.0 .0.to_f64_lossy() * rhs).ceil()).into() @@ -174,21 +158,3 @@ impl From for U256 { value.0.into() } } - -impl Add for EffectiveGasPrice { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl Zero for EffectiveGasPrice { - fn zero() -> Self { - Self(Ether::zero()) - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} diff --git a/crates/driver/src/infra/blockchain/mod.rs b/crates/driver/src/infra/blockchain/mod.rs index e2c930a1a9..f6f1d9bca8 100644 --- a/crates/driver/src/infra/blockchain/mod.rs +++ b/crates/driver/src/infra/blockchain/mod.rs @@ -1,7 +1,7 @@ use { self::contracts::ContractAt, crate::{boundary, domain::eth}, - ethcontract::dyns::DynWeb3, + ethcontract::{dyns::DynWeb3, errors::ExecutionError}, ethrpc::block_stream::CurrentBlockWatcher, std::{fmt, sync::Arc}, thiserror::Error, @@ -13,8 +13,6 @@ pub mod contracts; pub mod gas; pub mod token; -use {ethcontract::errors::ExecutionError, gas_estimation::GasPriceEstimating}; - pub use self::{contracts::Contracts, gas::GasPriceEstimator}; /// An Ethereum RPC connection. @@ -169,10 +167,6 @@ impl Ethereum { Ok(access_list.into()) } - pub fn boundary_gas_estimator(&self) -> Arc { - self.inner.gas.gas.clone() - } - /// Estimate gas used by a transaction. pub async fn estimate_gas(&self, tx: ð::Tx) -> Result { self.web3 diff --git a/crates/ethrpc/src/extensions.rs b/crates/ethrpc/src/extensions.rs index 6f4cf6bdb7..78f2d77eea 100644 --- a/crates/ethrpc/src/extensions.rs +++ b/crates/ethrpc/src/extensions.rs @@ -1,7 +1,7 @@ //! Module containing Ethereum RPC extension methods. use { - serde::{Deserialize, Serialize}, + serde::Serialize, std::collections::HashMap, web3::{ self, @@ -50,7 +50,7 @@ where pub type StateOverrides = HashMap; /// State override object. -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StateOverride { /// Fake balance to set for the account before executing the call. diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index e60f82fb01..23a13d7729 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -5,7 +5,6 @@ pub mod fee_policy; pub mod interaction; pub mod order; pub mod quote; -pub mod ratio_as_decimal; pub mod signature; pub mod solver_competition; pub mod time; diff --git a/crates/model/src/order.rs b/crates/model/src/order.rs index 4d3ca859a7..ec19f1caff 100644 --- a/crates/model/src/order.rs +++ b/crates/model/src/order.rs @@ -81,12 +81,6 @@ impl Order { pub fn is_limit_order(&self) -> bool { matches!(self.metadata.class, OrderClass::Limit) } - - /// For some orders the protocol doesn't precompute a fee. Instead solvers - /// are supposed to compute a reasonable fee themselves. - pub fn solver_determines_fee(&self) -> bool { - self.is_limit_order() - } } #[derive(Clone, Default, Debug)] @@ -133,11 +127,6 @@ impl OrderBuilder { self } - pub fn with_full_fee_amount(mut self, full_fee_amount: U256) -> Self { - self.0.metadata.full_fee_amount = full_fee_amount; - self - } - pub fn with_kind(mut self, kind: OrderKind) -> Self { self.0.data.kind = kind; self @@ -195,16 +184,6 @@ impl OrderBuilder { self } - pub fn with_solver_fee(mut self, fee: U256) -> Self { - self.0.metadata.solver_fee = fee; - self - } - - pub fn with_full_app_data(mut self, full_app_data: String) -> Self { - self.0.metadata.full_app_data = Some(full_app_data); - self - } - pub fn build(self) -> Order { self.0 } diff --git a/crates/model/src/quote.rs b/crates/model/src/quote.rs index 63d2409fea..0e7b2ac913 100644 --- a/crates/model/src/quote.rs +++ b/crates/model/src/quote.rs @@ -61,13 +61,6 @@ impl QuoteSigningScheme { _ => 0u64, } } - - pub fn new_eip1271_with_default_gas(onchain_order: bool) -> Self { - QuoteSigningScheme::Eip1271 { - onchain_order, - verification_gas_limit: default_verification_gas_limit(), - } - } } #[serde_as] @@ -302,19 +295,6 @@ pub struct OrderQuoteResponse { pub verified: bool, } -impl OrderQuoteRequest { - /// This method is used by the old, deprecated, fee endpoint to convert - /// {Buy, Sell}Requests - pub fn new(sell_token: H160, buy_token: H160, side: OrderQuoteSide) -> Self { - Self { - sell_token, - buy_token, - side, - ..Default::default() - } - } -} - #[cfg(test)] mod tests { use {super::*, serde_json::json, testlib::assert_json_matches}; diff --git a/crates/model/src/ratio_as_decimal.rs b/crates/model/src/ratio_as_decimal.rs deleted file mode 100644 index b19af6b3a9..0000000000 --- a/crates/model/src/ratio_as_decimal.rs +++ /dev/null @@ -1,110 +0,0 @@ -use { - bigdecimal::BigDecimal, - num::{BigInt, BigRational}, - serde::{de, Deserialize, Deserializer, Serializer}, - serde_with::{DeserializeAs, SerializeAs}, - std::{borrow::Cow, convert::TryInto, str::FromStr}, -}; - -pub struct DecimalBigRational; - -impl<'de> DeserializeAs<'de, BigRational> for DecimalBigRational { - fn deserialize_as(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize(deserializer) - } -} - -impl SerializeAs for DecimalBigRational { - fn serialize_as(source: &BigRational, serializer: S) -> Result - where - S: Serializer, - { - serialize(source, serializer) - } -} - -pub fn serialize(value: &BigRational, serializer: S) -> Result -where - S: Serializer, -{ - let top_bytes = value.numer().to_bytes_le(); - let top = BigInt::from_bytes_le(top_bytes.0, &top_bytes.1); - - let bottom_bytes = value.denom().to_bytes_le(); - let bottom = BigInt::from_bytes_le(bottom_bytes.0, &bottom_bytes.1); - let decimal = BigDecimal::from(top) / BigDecimal::from(bottom); - serializer.serialize_str(&decimal.to_string()) -} - -pub fn deserialize<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let big_decimal = BigDecimal::from_str(&Cow::::deserialize(deserializer)?) - .map_err(|err| de::Error::custom(format!("failed to decode decimal BigDecimal: {err}")))?; - let (x, exp) = big_decimal.into_bigint_and_exponent(); - let numerator_bytes = x.to_bytes_le(); - let base = num::bigint::BigInt::from_bytes_le(numerator_bytes.0, &numerator_bytes.1); - let ten = BigRational::new(10.into(), 1.into()); - let numerator = BigRational::new(base, 1.into()); - Ok(numerator - / ten.pow( - exp.try_into() - .map_err(|err| de::Error::custom(format!("decimal exponent overflow: {err}")))?, - )) -} - -#[cfg(test)] -mod tests { - use { - super::*, - num::{BigRational, Zero}, - serde_json::{json, value::Serializer}, - testlib::assert_json_matches, - }; - - #[test] - fn serializer() { - assert_json_matches!( - serialize(&BigRational::from_float(1.2).unwrap(), Serializer).unwrap(), - json!("1.1999999999999999555910790149937383830547332763671875") - ); - assert_json_matches!( - serialize( - &BigRational::new(1.into(), 3.into()), - Serializer - ) - .unwrap(), - json!("0.3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333") - ); - assert_json_matches!( - serialize(&BigRational::zero(), Serializer).unwrap(), - json!("0") - ); - assert_json_matches!( - serialize(&BigRational::new((-1).into(), 1.into()), Serializer).unwrap(), - json!("-1") - ); - } - - #[test] - fn deserialize_err() { - assert!(deserialize(json!("hello")).is_err()); - } - - #[test] - fn deserialize_ok() { - assert_eq!( - deserialize(json!("1.2")).unwrap(), - BigRational::new(12.into(), 10.into()) - ); - assert_eq!(deserialize(json!("0")).unwrap(), BigRational::zero()); - assert_eq!( - deserialize(json!("-1")).unwrap(), - BigRational::new((-1).into(), 1.into()) - ); - } -} diff --git a/crates/orderbook/src/api/get_native_price.rs b/crates/orderbook/src/api/get_native_price.rs index 6b00fc8810..7b7e213236 100644 --- a/crates/orderbook/src/api/get_native_price.rs +++ b/crates/orderbook/src/api/get_native_price.rs @@ -13,12 +13,6 @@ struct PriceResponse { price: f64, } -impl From for PriceResponse { - fn from(price: f64) -> Self { - Self { price } - } -} - fn get_native_prices_request() -> impl Filter + Clone { warp::path!("v1" / "token" / H160 / "native_price").and(warp::get()) } @@ -31,10 +25,9 @@ pub fn get_native_price( async move { let result = estimator.estimate_native_price(token).await; let reply = match result { - Ok(price) => with_status( - warp::reply::json(&PriceResponse::from(price)), - StatusCode::OK, - ), + Ok(price) => { + with_status(warp::reply::json(&PriceResponse { price }), StatusCode::OK) + } Err(err) => err.into_warp_reply(), }; Result::<_, Infallible>::Ok(reply) diff --git a/crates/orderbook/src/dto/auction.rs b/crates/orderbook/src/dto/auction.rs index b169a6f599..a132bbf747 100644 --- a/crates/orderbook/src/dto/auction.rs +++ b/crates/orderbook/src/dto/auction.rs @@ -24,7 +24,7 @@ pub struct Auction { pub type AuctionId = i64; #[serde_as] -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct AuctionWithId { /// Increments whenever the backend updates the auction. diff --git a/crates/shared/src/code_simulation.rs b/crates/shared/src/code_simulation.rs index 5d720ce278..7035c2dfb6 100644 --- a/crates/shared/src/code_simulation.rs +++ b/crates/shared/src/code_simulation.rs @@ -15,7 +15,6 @@ use { }; /// Simulate a call with state overrides. -#[mockall::automock] #[async_trait::async_trait] pub trait CodeSimulating: Send + Sync + 'static { async fn simulate( diff --git a/crates/shared/src/external_prices.rs b/crates/shared/src/external_prices.rs index 16886292bd..95109e318e 100644 --- a/crates/shared/src/external_prices.rs +++ b/crates/shared/src/external_prices.rs @@ -11,7 +11,7 @@ use { ethcontract::{H160, U256}, lazy_static::lazy_static, model::order::BUY_ETH_ADDRESS, - num::{BigInt, BigRational, CheckedDiv, One as _, ToPrimitive as _}, + num::{BigInt, BigRational, One as _, ToPrimitive as _}, std::collections::{BTreeMap, HashMap}, }; @@ -64,61 +64,6 @@ impl ExternalPrices { pub fn price(&self, token: &H160) -> Option<&BigRational> { self.0.get(token) } - - /// Converts a token amount into its native asset equivalent. - /// - /// # Panic - /// - /// This method panics if the specified token does not have a price. - pub fn get_native_amount(&self, token: H160, amount: BigRational) -> BigRational { - self.try_get_native_amount(token, amount) - .unwrap_or_else(|| panic!("missing price for {token}")) - } - - /// Converts a token amount into its native asset equivalent. - /// - /// This method is similar to [`get_native_amount`] except that it will - /// return `None` if the specified token does not have a price instead of - /// panicking. - pub fn try_get_native_amount(&self, token: H160, amount: BigRational) -> Option { - Some(self.0.get(&token)? * amount) - } - - /// Converts a native asset amount into an amount of equivalent value in the - /// specified token. - /// - /// # Panic - /// - /// This method panics if the specified token does not have a price. - pub fn get_token_amount(&self, amount: &BigRational, token: H160) -> BigRational { - self.try_get_token_amount(amount, token) - .unwrap_or_else(|| panic!("missing price for {token}")) - } - - /// Converts a native asset amount into an amount of equivalent value in the - /// specified token. - /// - /// This method is similar to [`get_native_amount`] except that it will - /// return `None` if the specified token does not have a price instead of - /// panicking. - pub fn try_get_token_amount(&self, amount: &BigRational, token: H160) -> Option { - amount.checked_div(self.0.get(&token)?) - } - - /// Converts a set of external prices into prices for the HTTP solver. - /// - /// Specifically the HTTP solver expects prices to be in `f64` and there not - /// to be an entry for `BUY_ETH_ADDRESS` since orders are already normalized - /// to use the native wrapped token. - pub fn into_http_solver_prices(&self) -> HashMap { - let mut prices = self - .0 - .iter() - .filter_map(|(token, price)| Some((*token, price.to_f64()?))) - .collect::>(); - prices.remove(&BUY_ETH_ADDRESS); - prices - } } impl Default for ExternalPrices { diff --git a/crates/shared/src/gas_price_estimation.rs b/crates/shared/src/gas_price_estimation.rs index 0f821c1a24..5d4e15ff34 100644 --- a/crates/shared/src/gas_price_estimation.rs +++ b/crates/shared/src/gas_price_estimation.rs @@ -106,7 +106,7 @@ pub async fn create_priority_estimator( Ok(PriorityGasPriceEstimating::new(estimators)) } -pub fn is_mainnet(network_id: &str) -> bool { +fn is_mainnet(network_id: &str) -> bool { network_id == "1" } diff --git a/crates/shared/src/http_client.rs b/crates/shared/src/http_client.rs index 7bbbd6e2c0..59df91d89a 100644 --- a/crates/shared/src/http_client.rs +++ b/crates/shared/src/http_client.rs @@ -1,6 +1,5 @@ use { - anyhow::{anyhow, Result}, - reqwest::{Client, ClientBuilder, Response}, + reqwest::{Client, ClientBuilder}, std::{ fmt::{self, Display, Formatter}, time::Duration, @@ -74,45 +73,3 @@ impl Display for Arguments { writeln!(f, "http_timeout: {:?}", http_timeout) } } - -/// Extracts the bytes of the response up to some size limit. -/// -/// Returns an error if the byte limit was exceeded. -pub async fn response_body_with_size_limit( - response: &mut Response, - limit: usize, -) -> Result> { - let mut bytes = Vec::new(); - while let Some(chunk) = response.chunk().await? { - let slice: &[u8] = &chunk; - if bytes.len() + slice.len() > limit { - return Err(anyhow!("size limit exceeded")); - } - bytes.extend_from_slice(slice); - } - Ok(bytes) -} - -#[cfg(test)] -mod tests { - use {super::*, reqwest::Client}; - - #[tokio::test] - #[ignore] - async fn real() { - let client = Client::default(); - - let mut response = client.get("https://cow.fi").send().await.unwrap(); - let bytes = response_body_with_size_limit(&mut response, 10).await; - dbg!(&bytes); - assert!(bytes.is_err()); - - let mut response = client.get("https://cow.fi").send().await.unwrap(); - let bytes = response_body_with_size_limit(&mut response, 1_000_000) - .await - .unwrap(); - dbg!(bytes.len()); - let text = std::str::from_utf8(&bytes).unwrap(); - dbg!(text); - } -} diff --git a/crates/shared/src/http_solver.rs b/crates/shared/src/http_solver.rs index 20843f6b66..805e2d84ab 100644 --- a/crates/shared/src/http_solver.rs +++ b/crates/shared/src/http_solver.rs @@ -1,16 +1,8 @@ pub mod model { - use { - ethcontract::H160, - number::serialization::HexOrDecimalU256, - primitive_types::U256, - serde::{Deserialize, Serialize}, - serde_with::serde_as, - }; + use {ethcontract::H160, primitive_types::U256, serde::Serialize}; - #[serde_as] - #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] + #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct TokenAmount { - #[serde_as(as = "HexOrDecimalU256")] pub amount: U256, pub token: H160, } diff --git a/crates/shared/src/interaction.rs b/crates/shared/src/interaction.rs index e6431f6c31..7caa026da2 100644 --- a/crates/shared/src/interaction.rs +++ b/crates/shared/src/interaction.rs @@ -1,8 +1,7 @@ use { - ethcontract::{transaction::TransactionBuilder, Bytes}, + ethcontract::Bytes, model::interaction::InteractionData, primitive_types::{H160, U256}, - web3::Transport, }; pub trait Interaction: std::fmt::Debug + Send + Sync { @@ -36,14 +35,3 @@ impl Interaction for InteractionData { (self.target, self.value, Bytes(self.call_data.clone())) } } - -pub fn for_transaction(tx: TransactionBuilder) -> InteractionData -where - T: Transport, -{ - InteractionData { - target: tx.to.unwrap(), - value: tx.value.unwrap_or_default(), - call_data: tx.data.unwrap().0, - } -} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index ed325a2f4e..6d69e4ae11 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -40,20 +40,6 @@ pub mod trade_finding; pub mod url; pub mod zeroex_api; -use std::{ - future::Future, - time::{Duration, Instant}, -}; - -/// Run a future and callback with the time the future took. The call back can -/// for example log the time. -pub async fn measure_time(future: impl Future, timer: impl FnOnce(Duration)) -> T { - let start = Instant::now(); - let result = future.await; - timer(start.elapsed()); - result -} - /// anyhow errors are not clonable natively. This is a workaround that creates a /// new anyhow error based on formatting the error with its inner sources /// without backtrace. diff --git a/crates/shared/src/maintenance.rs b/crates/shared/src/maintenance.rs index 2775bb7048..3c489f95cd 100644 --- a/crates/shared/src/maintenance.rs +++ b/crates/shared/src/maintenance.rs @@ -25,10 +25,6 @@ impl ServiceMaintenance { } } - pub fn spawn_background_task(self, block_stream: CurrentBlockWatcher) { - tokio::task::spawn(self.run_maintenance_on_new_block(block_stream)); - } - async fn run_maintenance_for_blocks(self, blocks: impl Stream) { self.metrics .runs diff --git a/crates/shared/src/price_estimation/mod.rs b/crates/shared/src/price_estimation/mod.rs index 8355acef1c..9ba6405f9a 100644 --- a/crates/shared/src/price_estimation/mod.rs +++ b/crates/shared/src/price_estimation/mod.rs @@ -1,7 +1,6 @@ use { crate::{ arguments::{display_option, display_secret_option, ExternalSolver}, - conversions::U256Ext, trade_finding::Interaction, }, anyhow::Result, @@ -10,7 +9,6 @@ use { futures::future::BoxFuture, itertools::Itertools, model::order::{BuyTokenDestination, OrderKind, SellTokenSource}, - num::BigRational, number::nonzero::U256 as NonZeroU256, rate_limit::{RateLimiter, Strategy}, reqwest::Url, @@ -477,22 +475,6 @@ impl Estimate { } } - /// The resulting price is how many units of sell_token needs to be sold for - /// one unit of buy_token (sell_amount / buy_amount). - pub fn price_in_sell_token_rational(&self, query: &Query) -> Option { - let (sell_amount, buy_amount) = self.amounts(query); - amounts_to_price(sell_amount, buy_amount) - } - - /// The price for the estimate denominated in sell token. - /// - /// The resulting price is how many units of sell_token needs to be sold for - /// one unit of buy_token (sell_amount / buy_amount). - pub fn price_in_sell_token_f64(&self, query: &Query) -> f64 { - let (sell_amount, buy_amount) = self.amounts(query); - sell_amount.to_f64_lossy() / buy_amount.to_f64_lossy() - } - /// The price of the estimate denominated in buy token. /// /// The resulting price is how many units of buy_token are bought for one @@ -510,16 +492,6 @@ pub trait PriceEstimating: Send + Sync + 'static { fn estimate(&self, query: Arc) -> BoxFuture<'_, PriceEstimateResult>; } -pub fn amounts_to_price(sell_amount: U256, buy_amount: U256) -> Option { - if buy_amount.is_zero() { - return None; - } - Some(BigRational::new( - sell_amount.to_big_int(), - buy_amount.to_big_int(), - )) -} - pub const HEALTHY_PRICE_ESTIMATION_TIME: Duration = Duration::from_millis(5_000); pub async fn rate_limited( diff --git a/crates/shared/src/trade_finding/mod.rs b/crates/shared/src/trade_finding/mod.rs index f8e589c467..4f0fbbacf0 100644 --- a/crates/shared/src/trade_finding/mod.rs +++ b/crates/shared/src/trade_finding/mod.rs @@ -11,7 +11,7 @@ use { }, anyhow::{Context, Result}, derive_more::Debug, - ethcontract::{contract::MethodBuilder, tokens::Tokenize, web3::Transport, Bytes, H160, U256}, + ethcontract::{Bytes, H160, U256}, model::{interaction::InteractionData, order::OrderKind}, num::CheckedDiv, number::conversions::big_rational_to_u256, @@ -24,7 +24,6 @@ use { /// /// This is similar to the `PriceEstimating` interface, but it expects calldata /// to also be produced. -#[mockall::automock] #[async_trait::async_trait] pub trait TradeFinding: Send + Sync + 'static { async fn get_quote(&self, query: &Query) -> Result; @@ -97,12 +96,6 @@ impl TradeKind { } } -impl Default for TradeKind { - fn default() -> Self { - TradeKind::Legacy(LegacyTrade::default()) - } -} - /// A legacy trade. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct LegacyTrade { @@ -185,18 +178,6 @@ pub struct Interaction { } impl Interaction { - pub fn from_call(method: MethodBuilder) -> Interaction - where - T: Transport, - R: Tokenize, - { - Interaction { - target: method.tx.to.unwrap(), - value: method.tx.value.unwrap_or_default(), - data: method.tx.data.unwrap().0, - } - } - pub fn encode(&self) -> EncodedInteraction { (self.target, self.value, Bytes(self.data.clone())) } diff --git a/crates/solver/src/liquidity/mod.rs b/crates/solver/src/liquidity/mod.rs index 6dc909c640..1af74c86d4 100644 --- a/crates/solver/src/liquidity/mod.rs +++ b/crates/solver/src/liquidity/mod.rs @@ -47,32 +47,6 @@ pub enum Liquidity { Concentrated(ConcentratedLiquidity), } -impl Liquidity { - /// Returns an iterator over all token pairs for the given liquidity. - pub fn all_token_pairs(&self) -> Vec { - match self { - Liquidity::ConstantProduct(amm) => vec![amm.tokens], - Liquidity::BalancerWeighted(amm) => token_pairs(&amm.reserves), - Liquidity::BalancerStable(amm) => token_pairs(&amm.reserves), - Liquidity::LimitOrder(order) => TokenPair::new(order.sell_token, order.buy_token) - .map(|pair| vec![pair]) - .unwrap_or_default(), - Liquidity::Concentrated(amm) => vec![amm.tokens], - } - } - - /// Returns the pool address on the blockchain containing this liquidity - pub fn address(&self) -> Option { - match self { - Liquidity::ConstantProduct(amm) => Some(amm.address), - Liquidity::BalancerWeighted(amm) => Some(amm.address), - Liquidity::BalancerStable(amm) => Some(amm.address), - Liquidity::LimitOrder(_) => None, - Liquidity::Concentrated(amm) => Some(amm.pool.address), - } - } -} - /// A trait associating some liquidity model to how it is executed and encoded /// in a settlement (through a `SettlementHandling` reference). This allows /// different liquidity types to be modeled the same way. @@ -148,19 +122,6 @@ impl Default for LimitOrderId { } } -impl LimitOrderId { - pub fn order_uid(&self) -> Option { - match self { - LimitOrderId::Market(uid) => Some(*uid), - LimitOrderId::Limit(uid) => Some(*uid), - LimitOrderId::Liquidity(order) => match order { - LiquidityOrderId::Protocol(uid) => Some(*uid), - LiquidityOrderId::ZeroEx(_) => None, - }, - } - } -} - #[cfg(test)] impl From for LimitOrderId { fn from(uid: u32) -> Self { @@ -190,16 +151,6 @@ pub struct LimitOrder { } impl LimitOrder { - pub fn is_liquidity_order(&self) -> bool { - matches!(self.id, LimitOrderId::Liquidity(_)) - } - - /// For some orders the protocol doesn't precompute a fee. Instead solvers - /// are supposed to compute a reasonable fee themselves. - pub fn solver_determines_fee(&self) -> bool { - matches!(self.id, LimitOrderId::Limit(_)) - } - /// Returns the full execution amount for the specified limit order. pub fn full_execution_amount(&self) -> U256 { match self.kind { @@ -368,19 +319,6 @@ pub struct StablePoolOrder { pub settlement_handling: Arc>, } -impl StablePoolOrder { - /// See [`shared::sources::balancer_v2::swap::StablePoolRef::reserves_without_bpt`]. - pub fn reserves_without_bpt(&self) -> impl Iterator + '_ { - shared::sources::balancer_v2::swap::StablePoolRef { - address: self.address, - reserves: &self.reserves, - swap_fee: self.fee, - amplification_parameter: self.amplification_parameter, - } - .reserves_without_bpt() - } -} - impl std::fmt::Debug for StablePoolOrder { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Stable Pool AMM {:?}", self.reserves.keys()) @@ -406,12 +344,6 @@ pub struct AmmOrderExecution { pub internalizable: bool, } -impl ConstantProductOrder { - pub fn constant_product(&self) -> U256 { - U256::from(self.reserves.0) * U256::from(self.reserves.1) - } -} - impl Settleable for ConstantProductOrder { type Execution = AmmOrderExecution; @@ -461,45 +393,6 @@ impl Settleable for ConcentratedLiquidity { } } -#[cfg(test)] -impl Default for ConstantProductOrder { - fn default() -> Self { - ConstantProductOrder { - address: Default::default(), - tokens: Default::default(), - reserves: Default::default(), - fee: num::Zero::zero(), - settlement_handling: tests::CapturingSettlementHandler::arc(), - } - } -} - -#[cfg(test)] -impl Default for WeightedProductOrder { - fn default() -> Self { - WeightedProductOrder { - address: Default::default(), - reserves: Default::default(), - fee: Bfp::zero(), - version: Default::default(), - settlement_handling: tests::CapturingSettlementHandler::arc(), - } - } -} - -#[cfg(test)] -impl Default for StablePoolOrder { - fn default() -> Self { - StablePoolOrder { - address: Default::default(), - reserves: Default::default(), - fee: Default::default(), - amplification_parameter: AmplificationParameter::new(1.into(), 1.into()).unwrap(), - settlement_handling: tests::CapturingSettlementHandler::arc(), - } - } -} - #[cfg(test)] pub mod tests { use {super::*, maplit::btreemap, std::sync::Mutex}; diff --git a/crates/solver/src/liquidity/slippage.rs b/crates/solver/src/liquidity/slippage.rs index 0d2566fd47..a4185aa909 100644 --- a/crates/solver/src/liquidity/slippage.rs +++ b/crates/solver/src/liquidity/slippage.rs @@ -23,11 +23,6 @@ pub struct SlippageContext<'a> { } impl SlippageContext<'_> { - /// Returns the external prices used for the slippage context. - pub fn prices(&self) -> &ExternalPrices { - self.prices - } - /// Applies slippage to the specified AMM execution. pub fn apply_to_amm_execution( &self, @@ -180,11 +175,6 @@ impl SlippageAmount { Ok(Self { relative, absolute }) } - /// Reduce the specified amount by the constant slippage. - pub fn sub_from_amount(&self, amount: U256) -> U256 { - amount.saturating_sub(self.absolute) - } - /// Increase the specified amount by the constant slippage. pub fn add_to_amount(&self, amount: U256) -> U256 { amount.saturating_add(self.absolute) diff --git a/crates/solver/src/liquidity_collector.rs b/crates/solver/src/liquidity_collector.rs index 9b0b866c7f..52a3fed2bc 100644 --- a/crates/solver/src/liquidity_collector.rs +++ b/crates/solver/src/liquidity_collector.rs @@ -8,7 +8,6 @@ use { tracing::Instrument, }; -#[mockall::automock] #[async_trait::async_trait] pub trait LiquidityCollecting: Send + Sync { async fn get_liquidity( diff --git a/crates/solver/src/settlement/mod.rs b/crates/solver/src/settlement/mod.rs index 66022b317c..01dda9c696 100644 --- a/crates/solver/src/settlement/mod.rs +++ b/crates/solver/src/settlement/mod.rs @@ -126,23 +126,6 @@ impl Settlement { .encode(execution, &mut self.encoder) } - #[cfg(test)] - pub fn with_trades(clearing_prices: HashMap, trades: Vec) -> Self { - let encoder = SettlementEncoder::with_trades(clearing_prices, trades); - Self { encoder } - } - - #[cfg(test)] - pub fn with_default_prices(trades: Vec) -> Self { - let clearing_prices = trades - .iter() - .flat_map(|trade| [trade.order.data.sell_token, trade.order.data.buy_token]) - .map(|token| (token, U256::from(1_000_000_000_000_000_000_u128))) - .collect(); - let encoder = SettlementEncoder::with_trades(clearing_prices, trades); - Self { encoder } - } - /// Returns the clearing prices map. pub fn clearing_prices(&self) -> &HashMap { self.encoder.clearing_prices() @@ -171,15 +154,6 @@ impl Settlement { }) } - /// Calculates the risk level for settlement to be reverted - pub fn revertable(&self) -> Revertable { - if self.encoder.has_interactions() { - Revertable::HighRisk - } else { - Revertable::NoRisk - } - } - pub fn encode(self, internalization_strategy: InternalizationStrategy) -> EncodedSettlement { self.encoder.finish(internalization_strategy) } diff --git a/crates/solver/src/settlement/settlement_encoder.rs b/crates/solver/src/settlement/settlement_encoder.rs index 35f1b3f21c..4a901c617e 100644 --- a/crates/solver/src/settlement/settlement_encoder.rs +++ b/crates/solver/src/settlement/settlement_encoder.rs @@ -71,15 +71,6 @@ enum TokenReference { }, } -impl Default for TokenReference { - fn default() -> Self { - Self::Indexed { - sell_token_index: 0, - buy_token_index: 0, - } - } -} - /// An trade that was added to the settlement encoder. #[derive(Clone, Debug, Eq, PartialEq)] struct EncoderTrade { @@ -166,12 +157,6 @@ impl SettlementEncoder { } } - pub(crate) fn has_interactions(&self) -> bool { - self.execution_plan - .iter() - .any(|(_, internalizable)| !internalizable) - } - /// Adds an order trade using the uniform clearing prices for sell and buy /// token. Fails if any used token doesn't have a price or if executed /// amount is impossible. diff --git a/crates/solvers/src/domain/auction.rs b/crates/solvers/src/domain/auction.rs index 26ac8cc104..c8173e1a79 100644 --- a/crates/solvers/src/domain/auction.rs +++ b/crates/solvers/src/domain/auction.rs @@ -31,10 +31,6 @@ impl Tokens { pub fn reference_price(&self, token: ð::TokenAddress) -> Option { self.get(token)?.reference_price } - - pub fn decimals(&self, token: ð::TokenAddress) -> Option { - self.get(token)?.decimals - } } /// The ID of an auction. diff --git a/crates/solvers/src/domain/eth/mod.rs b/crates/solvers/src/domain/eth/mod.rs index e469fb36ad..41de18f275 100644 --- a/crates/solvers/src/domain/eth/mod.rs +++ b/crates/solvers/src/domain/eth/mod.rs @@ -33,12 +33,6 @@ pub struct Asset { #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Ether(pub U256); -impl From for Ether { - fn from(value: U256) -> Self { - Self(value) - } -} - /// A token amount in wei, always representing the sell token of an order. #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, From)] pub struct SellTokenAmount(pub U256); @@ -57,14 +51,6 @@ impl From for SignedGas { #[derive(Clone, Copy, Debug, Default)] pub struct Gas(pub U256); -impl std::ops::Add for Gas { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - impl std::ops::Add for Gas { type Output = Self; @@ -84,18 +70,6 @@ pub type Rational = num::rational::Ratio; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Address(pub H160); -impl From for Address { - fn from(value: H160) -> Self { - Self(value) - } -} - -impl From
for H160 { - fn from(value: Address) -> Self { - value.0 - } -} - /// An onchain transaction. #[derive(Debug, Clone)] pub struct Tx { diff --git a/crates/solvers/src/domain/liquidity/limit_order.rs b/crates/solvers/src/domain/liquidity/limit_order.rs index 748160e30d..d2065f7a22 100644 --- a/crates/solvers/src/domain/liquidity/limit_order.rs +++ b/crates/solvers/src/domain/liquidity/limit_order.rs @@ -8,16 +8,6 @@ pub struct LimitOrder { pub fee: TakerAmount, } -impl LimitOrder { - /// Returns the fee amount as an asset. - pub fn fee(&self) -> eth::Asset { - eth::Asset { - token: self.taker.token, - amount: self.fee.0, - } - } -} - /// An amount denominated in the taker token of a [`LimitOrder`]. #[derive(Debug, Clone, Copy)] pub struct TakerAmount(pub U256); diff --git a/crates/solvers/src/domain/liquidity/weighted_product.rs b/crates/solvers/src/domain/liquidity/weighted_product.rs index e9c9bf6c4d..730369b050 100644 --- a/crates/solvers/src/domain/liquidity/weighted_product.rs +++ b/crates/solvers/src/domain/liquidity/weighted_product.rs @@ -42,15 +42,6 @@ impl Reserves { self.0.iter().cloned() } - /// Returns the reserve for the specified token. - pub fn get(&self, token: eth::TokenAddress) -> Option { - let index = self - .0 - .binary_search_by_key(&token, |reserve| reserve.asset.token) - .ok()?; - Some(self.0[index].clone()) - } - /// Returns an iterator over the tokens pairs handled by the pool reserves. pub fn token_pairs(&self) -> impl Iterator + '_ { self.0 diff --git a/crates/solvers/src/domain/order.rs b/crates/solvers/src/domain/order.rs index 54a84165bf..1696c2c87a 100644 --- a/crates/solvers/src/domain/order.rs +++ b/crates/solvers/src/domain/order.rs @@ -18,13 +18,6 @@ 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 `true` if the order expects a solver-computed fee. pub fn solver_determines_fee(&self) -> bool { self.class == Class::Limit @@ -110,16 +103,6 @@ pub enum Signature { PreSign, } -impl Signature { - pub fn to_bytes(&self) -> Vec { - match self { - Self::Eip712(signature) | Self::EthSign(signature) => signature.to_bytes().to_vec(), - Self::Eip1271(signature) => signature.clone(), - Self::PreSign => Vec::new(), - } - } -} - #[derive(Clone, Copy, Debug, Default)] pub struct EcdsaSignature { pub r: H256, diff --git a/crates/solvers/src/domain/solution.rs b/crates/solvers/src/domain/solution.rs index ea4e323c0f..7bd6883d6a 100644 --- a/crates/solvers/src/domain/solution.rs +++ b/crates/solvers/src/domain/solution.rs @@ -10,12 +10,6 @@ use { #[derive(Debug, Default, Copy, Clone)] pub struct Id(pub u64); -impl From for Id { - fn from(id: u64) -> Self { - Self(id) - } -} - /// A solution to an auction. #[derive(Debug, Default)] pub struct Solution { @@ -105,10 +99,6 @@ impl Solution { self } - - pub fn is_empty(&self) -> bool { - self.prices.0.is_empty() && self.trades.is_empty() && self.interactions.is_empty() - } } /// A solution for a settling a single order. diff --git a/crates/solvers/src/infra/blockchain.rs b/crates/solvers/src/infra/blockchain.rs deleted file mode 100644 index 9bed81083b..0000000000 --- a/crates/solvers/src/infra/blockchain.rs +++ /dev/null @@ -1,13 +0,0 @@ -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/metrics.rs b/crates/solvers/src/infra/metrics.rs index 34cf194c1f..509c8893a0 100644 --- a/crates/solvers/src/infra/metrics.rs +++ b/crates/solvers/src/infra/metrics.rs @@ -42,10 +42,6 @@ pub fn solved(deadline: &auction::Deadline, solutions: &[solution::Solution]) { get().solutions.inc_by(solutions.len() as u64); } -pub fn solve_error(reason: &str) { - get().solve_errors.with_label_values(&[reason]).inc(); -} - /// Get the metrics instance. fn get() -> &'static Metrics { Metrics::instance(observe::metrics::get_storage_registry()) diff --git a/crates/solvers/src/infra/mod.rs b/crates/solvers/src/infra/mod.rs index d0e1cfedc7..792a13c703 100644 --- a/crates/solvers/src/infra/mod.rs +++ b/crates/solvers/src/infra/mod.rs @@ -1,4 +1,3 @@ -pub mod blockchain; pub mod cli; pub mod config; pub mod contracts; diff --git a/crates/solvers/src/util/bytes.rs b/crates/solvers/src/util/bytes.rs index 1ff531c545..83a91f06d4 100644 --- a/crates/solvers/src/util/bytes.rs +++ b/crates/solvers/src/util/bytes.rs @@ -10,21 +10,3 @@ where write!(f, "0x{}", hex::encode(&self.0)) } } - -impl From for Bytes { - fn from(value: T) -> Self { - Self(value) - } -} - -impl From>> for Vec { - fn from(value: Bytes>) -> Self { - value.0 - } -} - -impl From> for [u8; N] { - fn from(value: Bytes<[u8; N]>) -> Self { - value.0 - } -} diff --git a/crates/solvers/src/util/conv.rs b/crates/solvers/src/util/conv.rs index 509a4180d2..e679205132 100644 --- a/crates/solvers/src/util/conv.rs +++ b/crates/solvers/src/util/conv.rs @@ -2,7 +2,7 @@ use { crate::domain::eth, - bigdecimal::{num_bigint::ToBigInt, BigDecimal}, + bigdecimal::BigDecimal, ethereum_types::U256, num::{rational::Ratio, BigInt, BigUint, One}, }; @@ -45,22 +45,6 @@ pub fn u256_to_biguint(i: &U256) -> BigUint { BigUint::from_bytes_be(&bytes) } -pub fn u256_to_bigdecimal(i: &U256) -> BigDecimal { - BigDecimal::new(u256_to_biguint(i).into(), 0) -} - -pub fn bigint_to_u256(i: &BigInt) -> Option { - if i.sign() == num::bigint::Sign::Minus { - return None; - } - biguint_to_u256(i.magnitude()) -} - -pub fn bigdecimal_to_u256(d: &BigDecimal) -> Option { - let d = d.to_bigint()?; - bigint_to_u256(&d) -} - /// Converts a `BigDecimal` amount in Ether units to wei. pub fn decimal_to_ether(d: &BigDecimal) -> Option { let scaled = d * BigDecimal::new(BigInt::one(), -18); diff --git a/crates/solvers/src/util/mod.rs b/crates/solvers/src/util/mod.rs index 49e817c0b9..ce85e8add0 100644 --- a/crates/solvers/src/util/mod.rs +++ b/crates/solvers/src/util/mod.rs @@ -3,4 +3,3 @@ pub mod conv; pub mod fmt; pub mod math; pub mod serialize; -pub mod url; diff --git a/crates/solvers/src/util/serialize/hex.rs b/crates/solvers/src/util/serialize/hex.rs deleted file mode 100644 index 0875628a27..0000000000 --- a/crates/solvers/src/util/serialize/hex.rs +++ /dev/null @@ -1,56 +0,0 @@ -use { - serde::{de, Deserialize, Deserializer, Serializer}, - serde_with::{DeserializeAs, SerializeAs}, - std::borrow::Cow, -}; - -/// Serialize and deserialize binary data as a hexadecimal string. -#[derive(Debug)] -pub struct Hex; - -impl<'de> DeserializeAs<'de, Vec> for Hex { - fn deserialize_as>(deserializer: D) -> Result, D::Error> { - let s = Cow::::deserialize(deserializer)?; - let s = s - .strip_prefix("0x") - .ok_or_else( - || format!("failed to decode {s:?} as a hex string: missing \"0x\" prefix",), - ) - .map_err(de::Error::custom)?; - hex::decode(s).map_err(|err| { - de::Error::custom(format!("failed to decode {s:?} as a hex string: {err}",)) - }) - } -} - -impl SerializeAs> for Hex { - fn serialize_as(source: &Vec, serializer: S) -> Result { - let hex = hex::encode(source); - serializer.serialize_str(&format!("0x{hex}")) - } -} - -impl<'de, const N: usize> DeserializeAs<'de, [u8; N]> for Hex { - fn deserialize_as>(deserializer: D) -> Result<[u8; N], D::Error> { - let s = Cow::::deserialize(deserializer)?; - let s = s - .strip_prefix("0x") - .ok_or_else( - || format!("failed to decode {s:?} as a hex string: missing \"0x\" prefix",), - ) - .map_err(de::Error::custom)?; - - let mut buffer = [0; N]; - hex::decode_to_slice(s, &mut buffer).map_err(|err| { - de::Error::custom(format!("failed to decode {s:?} as a hex string: {err}",)) - })?; - Ok(buffer) - } -} - -impl SerializeAs<[u8; N]> for Hex { - fn serialize_as(source: &[u8; N], serializer: S) -> Result { - let hex = hex::encode(source); - serializer.serialize_str(&format!("0x{hex}")) - } -} diff --git a/crates/solvers/src/util/serialize/mod.rs b/crates/solvers/src/util/serialize/mod.rs index 40a9110f94..fdfefaeb6a 100644 --- a/crates/solvers/src/util/serialize/mod.rs +++ b/crates/solvers/src/util/serialize/mod.rs @@ -1,4 +1,3 @@ -mod hex; mod u256; pub use self::u256::U256; diff --git a/crates/solvers/src/util/serialize/never.rs b/crates/solvers/src/util/serialize/never.rs deleted file mode 100644 index 5ff667e78f..0000000000 --- a/crates/solvers/src/util/serialize/never.rs +++ /dev/null @@ -1,25 +0,0 @@ -use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer}; - -/// A type that never deserializes or serializes. -/// -/// This can be used in situations where a generic type that implements `serde` -/// traits is required, but you don't want it to actually represent any data. -pub struct Never; - -impl<'de> Deserialize<'de> for Never { - fn deserialize(_: D) -> Result - where - D: Deserializer<'de>, - { - Err(de::Error::custom("neva eva eva")) - } -} - -impl Serialize for Never { - fn serialize(&self, _: S) -> Result - where - S: Serializer, - { - Err(ser::Error::custom("neva eva eva")) - } -} diff --git a/crates/solvers/src/util/serialize/str.rs b/crates/solvers/src/util/serialize/str.rs deleted file mode 100644 index 4500a7492c..0000000000 --- a/crates/solvers/src/util/serialize/str.rs +++ /dev/null @@ -1,10 +0,0 @@ -use {serde::Serializer, serde_with::SerializeAs}; - -/// Serializes a slice of strings as a comma-separated list. -pub struct CommaSeparated; - -impl SerializeAs> for CommaSeparated { - fn serialize_as(source: &Vec, serializer: S) -> Result { - serializer.serialize_str(&source.join(",")) - } -} diff --git a/crates/solvers/src/util/serialize/u256.rs b/crates/solvers/src/util/serialize/u256.rs index 61ebf14e22..4167e1c299 100644 --- a/crates/solvers/src/util/serialize/u256.rs +++ b/crates/solvers/src/util/serialize/u256.rs @@ -1,6 +1,6 @@ use { - serde::{de, Deserialize, Deserializer, Serializer}, - serde_with::{DeserializeAs, SerializeAs}, + serde::{de, Deserialize, Deserializer}, + serde_with::DeserializeAs, std::borrow::Cow, }; @@ -17,12 +17,3 @@ impl<'de> DeserializeAs<'de, ethereum_types::U256> for U256 { .map_err(|err| de::Error::custom(format!("failed to parse {s:?} as a U256: {err}"))) } } - -impl SerializeAs for U256 { - fn serialize_as( - value: ðereum_types::U256, - serializer: S, - ) -> Result { - serializer.serialize_str(&value.to_string()) - } -} diff --git a/crates/solvers/src/util/url.rs b/crates/solvers/src/util/url.rs index 7cc53c3970..e69de29bb2 100644 --- a/crates/solvers/src/util/url.rs +++ b/crates/solvers/src/util/url.rs @@ -1,14 +0,0 @@ -use reqwest::Url; - -/// Join a path with a URL, ensuring that there is only one slash between them. -/// It doesn't matter if the URL ends with a slash or the path starts with one. -pub fn join(url: &Url, mut path: &str) -> Url { - let mut url = url.to_string(); - while url.ends_with('/') { - url.pop(); - } - while path.starts_with('/') { - path = &path[1..] - } - Url::parse(&format!("{url}/{path}")).unwrap() -} From 546fa7004045d55a43f89e7da9c6027dab0b350d Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Thu, 5 Dec 2024 14:51:18 +0100 Subject: [PATCH 04/25] Introduce Component for Balance Overrides (#3125) # Description This PR is the first in a stack to add a system for overriding balances so that more quotes can be verified. One limitation with the current quote verification system, is that it requires that the `from` account in the quote has the sell token balance available (or available after a pre-hook is executed) in order for the quote to be properly verified. This isn't always possible for all flows (and notably flows at Safe, where transactions to prepare the balance happens at the same time as the `setPreSignature` transaction executes, so after the quote). The overall solution I would propose (hopefully a pragmatic one that isn't considered _too_ hacky) would be enable special handling for the most commonly traded tokens, by configuring for each token how the storage slot is computed for the token balance. This way, you could maintain a file that contains a `token => computation_strategy` map for the most popular tokens allowing trades to be verified even for quotes from users without the balance available. This PR is the first piece for this overall solution, which introduces a component for computing storage slots needed for overriding balances for `eth_call` simulations. If this strategy is accepted, in a follow up PRs I would: 1. Add the component to the trade verifier and use it to fund the trader when simulating quotes (I have an idea on how to do this: you would override the balance of the `Solver` simulation entrypoint, which would top up the `Trader` balance if needed; this way the missing balance can be reported as part of the simulation and logged). 2. Pipe configuration to the trade verifier and balance overrides component # Changes - [x] Introduces a new `BalanceOverriding` component ## How to test Added unit tests verifying logic. --- .../src/price_estimation/trade_verifier.rs | 2 + .../trade_verifier/balance_overrides.rs | 130 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 02e20a24d9..7107ac5ff0 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -1,3 +1,5 @@ +pub mod balance_overrides; + use { super::{Estimate, Verification}, crate::{ diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs new file mode 100644 index 0000000000..e8992efb6c --- /dev/null +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs @@ -0,0 +1,130 @@ +use { + ethcontract::{Address, H256, U256}, + ethrpc::extensions::StateOverride, + maplit::hashmap, + std::collections::HashMap, + web3::signing, +}; + +/// A component that can provide balance overrides for tokens. +/// +/// This allows a wider range of verified quotes to work, even when balances +/// are not available for the quoter. +pub trait BalanceOverriding { + fn state_override(&self, request: &BalanceOverrideRequest) -> Option; +} + +/// Parameters for computing a balance override request. +pub struct BalanceOverrideRequest { + /// The token for the override. + pub token: Address, + /// The account to override the balance for. + pub holder: Address, + /// The token amount (in atoms) to set the balance to. + pub amount: U256, +} + +/// A simple configuration-based balance override provider. +#[derive(Clone, Debug, Default)] +pub struct ConfigurationBalanceOverrides(HashMap); + +#[derive(Clone, Debug)] +pub enum Strategy { + Mapping { slot: U256 }, +} + +impl ConfigurationBalanceOverrides { + pub fn new(config: HashMap) -> Self { + Self(config) + } +} + +impl BalanceOverriding for ConfigurationBalanceOverrides { + fn state_override(&self, request: &BalanceOverrideRequest) -> Option { + let strategy = self.0.get(&request.token)?; + match strategy { + Strategy::Mapping { slot } => Some(StateOverride { + state_diff: Some(hashmap! { + address_mapping_storage_slot(slot, &request.holder) => request.amount, + }), + ..Default::default() + }), + } + } +} + +/// Computes the storage slot where the value is stored for Solidity mappings +/// of the form `mapping(address => ...)`. +/// +/// See . +fn address_mapping_storage_slot(slot: &U256, address: &Address) -> H256 { + let mut buf = [0; 64]; + buf[12..32].copy_from_slice(address.as_fixed_bytes()); + slot.to_big_endian(&mut buf[32..64]); + H256(signing::keccak256(&buf)) +} + +#[cfg(test)] +mod tests { + use {super::*, hex_literal::hex}; + + #[test] + fn balance_override_computation() { + let balance_overrides = ConfigurationBalanceOverrides::new(hashmap! { + addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB") => Strategy::Mapping { + slot: U256::from(0), + }, + }); + + assert_eq!( + balance_overrides.state_override(&BalanceOverrideRequest { + token: addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"), + holder: addr!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"), + amount: 0x42_u64.into(), + }), + Some(StateOverride { + state_diff: Some(hashmap! { + H256(hex!("fca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33")) => 0x42_u64.into() + }), + ..Default::default() + }), + ); + + // You can verify the state override computation is correct by running: + // ``` + // curl -X POST $RPC -H 'Content-Type: application/data' --data '{ + // "jsonrpc": "2.0", + // "id": 0, + // "method": "eth_call", + // "params": [ + // { + // "to": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", + // "data": "0x70a08231000000000000000000000000d8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + // }, + // "latest", + // { + // "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { + // "stateDiff": { + // "0xfca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33": + // "0x0000000000000000000000000000000000000000000000000000000000000042" + // } + // } + // } + // ] + // }' + // ``` + } + + #[test] + fn balance_overrides_none_for_unknown_tokens() { + let balance_overrides = ConfigurationBalanceOverrides::default(); + assert_eq!( + balance_overrides.state_override(&BalanceOverrideRequest { + token: addr!("0000000000000000000000000000000000000000"), + holder: addr!("0000000000000000000000000000000000000001"), + amount: U256::zero(), + }), + None, + ); + } +} From e75a277e922e6c50ea169eadb8e667927c14c831 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Thu, 5 Dec 2024 16:39:41 +0100 Subject: [PATCH 05/25] Simulated Token Balances for Verified Quotes (#3147) # Description This PR is a follow up to #3125 and uses the component introduced in the aforementioned PR for setting up a simulated token balance using state overrides in order for quote verification to work even when the trader does not have sufficient balance. # Changes The way it works is by configuring known mapping slots for the `mapping(address => uint256) balances` in ERC20 token contract implementations and using this to compute the slot for overriding a separate account's token balance (the `Spardose`), which can prefund the trader during quote simualtions. We intentionally do not override the trader's or solver's balance in order to not interfere with the settlement process: > Technically a solver could have private inventory they would like to > use for the solution instead of transfering it to the trader. _Posted by @MartinquaXD_ Note that the type of the state override changed slightly. This is because it was wrong to begin with. Node implementations I tested with (Geth and Anvil) expect both the slot and the value for state overrides to be exactly 32-bytes long (so `H256`). I guess this feature of the state override in the `ethrpc` crate was not used in the past and therefore no one noticed :shrug:. - [x] Adds command line configuration for token balance overrides - [x] Adds logic to the trades verifier to setup state overrides for take token balances for the configured tokens - [x] Adds logic in the settlement simulation to try and fund the trader if they are missing balances and balance overrides are enabled - [x] Fixes to the `StateOverride` type and serialization ### Test Plan Added an E2E test that uses the new token balance override feature in order to produce a verified quote for a trader with no balances. Note that commenting out the API arguments causes the test to fail as expected. > This PR is the combination of two original PRs mentioned in #3125: > * https://github.com/nlordell/cowprotocol-services/pull/1 > * https://github.com/nlordell/cowprotocol-services/pull/2. --- crates/contracts/artifacts/Solver.json | 2 +- crates/contracts/artifacts/Spardose.json | 1 + crates/contracts/artifacts/Swapper.json | 2 +- crates/contracts/artifacts/Trader.json | 2 +- crates/contracts/build.rs | 3 +- crates/contracts/solidity/Makefile | 1 + crates/contracts/solidity/Solver.sol | 36 +++++-- crates/contracts/solidity/Spardose.sol | 32 ++++++ crates/contracts/solidity/Trader.sol | 3 - .../solidity/libraries/SafeERC20.sol | 17 ++- crates/contracts/src/lib.rs | 1 + crates/e2e/tests/e2e/quote_verification.rs | 102 +++++++++++++++++- crates/ethrpc/src/extensions.rs | 4 +- crates/shared/src/code_simulation.rs | 17 +-- crates/shared/src/price_estimation/factory.rs | 27 ++--- crates/shared/src/price_estimation/mod.rs | 18 ++++ .../src/price_estimation/trade_verifier.rs | 70 ++++++++++-- .../trade_verifier/balance_overrides.rs | 79 ++++++++++++-- 18 files changed, 353 insertions(+), 64 deletions(-) create mode 100644 crates/contracts/artifacts/Spardose.json create mode 100644 crates/contracts/solidity/Spardose.sol diff --git a/crates/contracts/artifacts/Solver.json b/crates/contracts/artifacts/Solver.json index f4de20dccc..0b3ffa8e18 100644 --- a/crates/contracts/artifacts/Solver.json +++ b/crates/contracts/artifacts/Solver.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610941806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063cc6b67a914610050575b600080fd5b61004e610049366004610600565b61007a565b005b61006361005e3660046106d5565b6101af565b6040516100719291906107bf565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610142919061080d565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a6101879082610855565b6101939061116c61086e565b6000808282546101a3919061086e565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50505061034e88888f6103c9565b6103598d86866104a1565b915061036688888f6103c9565b60018054806020026020016040519081016040528092919081815260200182805480156103b257602002820191906000526020600020905b81548152602001906001019080831161039e575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d8585848181106103ec576103ec610881565b905060200201602081019061040191906108b0565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561047657600080fd5b505af115801561048a573d6000803e3d6000fd5b505050508080610499906108cd565b9150506103cc565b6000805a90506104fe84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610520565b506000545a61050d9083610855565b6105179190610855565b95945050505050565b606061052e83600084610535565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161055f9190610905565b60006040518083038185875af1925050503d806000811461059c576040519150601f19603f3d011682016040523d82523d6000602084013e6105a1565b606091505b5092509050806105b357815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105dd57600080fd5b50565b80356105eb816105bb565b919050565b803580151581146105eb57600080fd5b60008060006060848603121561061557600080fd5b8335610620816105bb565b92506020840135610630816105bb565b915061063e604085016105f0565b90509250925092565b60008083601f84011261065957600080fd5b50813567ffffffffffffffff81111561067157600080fd5b6020830191508360208260051b850101111561068c57600080fd5b9250929050565b60008083601f8401126106a557600080fd5b50813567ffffffffffffffff8111156106bd57600080fd5b60208301915083602082850101111561068c57600080fd5b60008060008060008060008060008060006101208c8e0312156106f757600080fd5b6107018c356105bb565b8b359a5061071260208d01356105bb565b60208c0135995061072560408d016105e0565b985060608c0135975061073a60808d016105e0565b965067ffffffffffffffff8060a08e0135111561075657600080fd5b6107668e60a08f01358f01610647565b909750955061077760c08e016105e0565b94508060e08e0135111561078a57600080fd5b5061079b8d60e08e01358e01610693565b90935091506107ad6101008d016105f0565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015610800578451835293830193918301916001016107e4565b5090979650505050505050565b60006020828403121561081f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561086857610868610826565b92915050565b8082018082111561086857610868610826565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108c257600080fd5b813561052e816105bb565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108fe576108fe610826565b5060010190565b6000825160005b81811015610926576020818601810151858301520161090c565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063cc6b67a914610050575b600080fd5b61004e610049366004610600565b61007a565b005b61006361005e3660046106d5565b6101af565b6040516100719291906107bf565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610142919061080d565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a6101879082610855565b6101939061116c61086e565b6000808282546101a3919061086e565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50505061034e88888f6103c9565b6103598d86866104a1565b915061036688888f6103c9565b60018054806020026020016040519081016040528092919081815260200182805480156103b257602002820191906000526020600020905b81548152602001906001019080831161039e575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d8585848181106103ec576103ec610881565b905060200201602081019061040191906108b0565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561047657600080fd5b505af115801561048a573d6000803e3d6000fd5b505050508080610499906108cd565b9150506103cc565b6000805a90506104fe84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610520565b506000545a61050d9083610855565b6105179190610855565b95945050505050565b606061052e83600084610535565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161055f9190610905565b60006040518083038185875af1925050503d806000811461059c576040519150601f19603f3d011682016040523d82523d6000602084013e6105a1565b606091505b5092509050806105b357815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105dd57600080fd5b50565b80356105eb816105bb565b919050565b803580151581146105eb57600080fd5b60008060006060848603121561061557600080fd5b8335610620816105bb565b92506020840135610630816105bb565b915061063e604085016105f0565b90509250925092565b60008083601f84011261065957600080fd5b50813567ffffffffffffffff81111561067157600080fd5b6020830191508360208260051b850101111561068c57600080fd5b9250929050565b60008083601f8401126106a557600080fd5b50813567ffffffffffffffff8111156106bd57600080fd5b60208301915083602082850101111561068c57600080fd5b60008060008060008060008060008060006101208c8e0312156106f757600080fd5b6107018c356105bb565b8b359a5061071260208d01356105bb565b60208c0135995061072560408d016105e0565b985060608c0135975061073a60808d016105e0565b965067ffffffffffffffff8060a08e0135111561075657600080fd5b6107668e60a08f01358f01610647565b909750955061077760c08e016105e0565b94508060e08e0135111561078a57600080fd5b5061079b8d60e08e01358e01610693565b90935091506107ad6101008d016105f0565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015610800578451835293830193918301916001016107e4565b5090979650505050505050565b60006020828403121561081f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561086857610868610826565b92915050565b8082018082111561086857610868610826565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108c257600080fd5b813561052e816105bb565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108fe576108fe610826565b5060010190565b6000825160005b81811015610926576020818601810151858301520161090c565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"address","name":"spardose","type":"address"}],"internalType":"struct Solver.Mock","name":"mock","type":"tuple"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610b26806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806317b86b161461003b5780633bbb2e1d14610065575b600080fd5b61004e610049366004610851565b61007a565b60405161005c92919061093c565b60405180910390f35b61007861007336600461098a565b6103cf565b005b60006060333014610112576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e000000000000000060648201526084015b60405180910390fd5b8251156102e4576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b15801561019957600080fd5b505af11580156101ad573d6000803e3d6000fd5b50505060208401516040517fc56cca8300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8f811660048301528e81166024830152604482018e9052909116915063c56cca83906064016020604051808303816000875af1158015610234573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061025891906109d5565b6102e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610109565b60008673ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461033f576040519150601f19603f3d011682016040523d82523d6000602084013e610344565b606091505b505090505061035488888f610504565b61035f8d86866105dc565b915061036c88888f610504565b60018054806020026020016040519081016040528092919081815260200182805480156103b857602002820191906000526020600020905b8154815260200190600101908083116103a4575b505050505090509b509b9950505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff86161461049c576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa158015610473573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049791906109f2565b6104b5565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156104fe575a6104dc9082610a3a565b6104e89061116c610a53565b6000808282546104f89190610a53565b90915550505b50505050565b60005b828110156104fe5730633bbb2e1d85858481811061052757610527610a66565b905060200201602081019061053c9190610a95565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156105b157600080fd5b505af11580156105c5573d6000803e3d6000fd5b5050505080806105d490610ab2565b915050610507565b6000805a905061063984848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff89169291505061065b565b506000545a6106489083610a3a565b6106529190610a3a565b95945050505050565b606061066983600084610670565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161069a9190610aea565b60006040518083038185875af1925050503d80600081146106d7576040519150601f19603f3d011682016040523d82523d6000602084013e6106dc565b606091505b5092509050806106ee57815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461071857600080fd5b50565b8035610726816106f6565b919050565b60008083601f84011261073d57600080fd5b50813567ffffffffffffffff81111561075557600080fd5b6020830191508360208260051b850101111561077057600080fd5b9250929050565b60008083601f84011261078957600080fd5b50813567ffffffffffffffff8111156107a157600080fd5b60208301915083602082850101111561077057600080fd5b801515811461071857600080fd5b6000604082840312156107d957600080fd5b6040516040810181811067ffffffffffffffff82111715610823577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040529050808235610834816107b9565b81526020830135610844816106f6565b6020919091015292915050565b60008060008060008060008060008060006101408c8e03121561087357600080fd5b61087d8c356106f6565b8b359a5061088e60208d01356106f6565b60208c013599506108a160408d0161071b565b985060608c013597506108b660808d0161071b565b965067ffffffffffffffff8060a08e013511156108d257600080fd5b6108e28e60a08f01358f0161072b565b90975095506108f360c08e0161071b565b94508060e08e0135111561090657600080fd5b506109178d60e08e01358e01610777565b909350915061092a8d6101008e016107c7565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561097d57845183529383019391830191600101610961565b5090979650505050505050565b60008060006060848603121561099f57600080fd5b83356109aa816106f6565b925060208401356109ba816106f6565b915060408401356109ca816107b9565b809150509250925092565b6000602082840312156109e757600080fd5b8151610669816107b9565b600060208284031215610a0457600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610a4d57610a4d610a0b565b92915050565b80820180821115610a4d57610a4d610a0b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208284031215610aa757600080fd5b8135610669816106f6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610ae357610ae3610a0b565b5060010190565b6000825160005b81811015610b0b5760208186018101518583015201610af1565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317b86b161461003b5780633bbb2e1d14610065575b600080fd5b61004e610049366004610851565b61007a565b60405161005c92919061093c565b60405180910390f35b61007861007336600461098a565b6103cf565b005b60006060333014610112576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e000000000000000060648201526084015b60405180910390fd5b8251156102e4576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b15801561019957600080fd5b505af11580156101ad573d6000803e3d6000fd5b50505060208401516040517fc56cca8300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8f811660048301528e81166024830152604482018e9052909116915063c56cca83906064016020604051808303816000875af1158015610234573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061025891906109d5565b6102e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610109565b60008673ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461033f576040519150601f19603f3d011682016040523d82523d6000602084013e610344565b606091505b505090505061035488888f610504565b61035f8d86866105dc565b915061036c88888f610504565b60018054806020026020016040519081016040528092919081815260200182805480156103b857602002820191906000526020600020905b8154815260200190600101908083116103a4575b505050505090509b509b9950505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff86161461049c576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa158015610473573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049791906109f2565b6104b5565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156104fe575a6104dc9082610a3a565b6104e89061116c610a53565b6000808282546104f89190610a53565b90915550505b50505050565b60005b828110156104fe5730633bbb2e1d85858481811061052757610527610a66565b905060200201602081019061053c9190610a95565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156105b157600080fd5b505af11580156105c5573d6000803e3d6000fd5b5050505080806105d490610ab2565b915050610507565b6000805a905061063984848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff89169291505061065b565b506000545a6106489083610a3a565b6106529190610a3a565b95945050505050565b606061066983600084610670565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161069a9190610aea565b60006040518083038185875af1925050503d80600081146106d7576040519150601f19603f3d011682016040523d82523d6000602084013e6106dc565b606091505b5092509050806106ee57815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461071857600080fd5b50565b8035610726816106f6565b919050565b60008083601f84011261073d57600080fd5b50813567ffffffffffffffff81111561075557600080fd5b6020830191508360208260051b850101111561077057600080fd5b9250929050565b60008083601f84011261078957600080fd5b50813567ffffffffffffffff8111156107a157600080fd5b60208301915083602082850101111561077057600080fd5b801515811461071857600080fd5b6000604082840312156107d957600080fd5b6040516040810181811067ffffffffffffffff82111715610823577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040529050808235610834816107b9565b81526020830135610844816106f6565b6020919091015292915050565b60008060008060008060008060008060006101408c8e03121561087357600080fd5b61087d8c356106f6565b8b359a5061088e60208d01356106f6565b60208c013599506108a160408d0161071b565b985060608c013597506108b660808d0161071b565b965067ffffffffffffffff8060a08e013511156108d257600080fd5b6108e28e60a08f01358f0161072b565b90975095506108f360c08e0161071b565b94508060e08e0135111561090657600080fd5b506109178d60e08e01358e01610777565b909350915061092a8d6101008e016107c7565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561097d57845183529383019391830191600101610961565b5090979650505050505050565b60008060006060848603121561099f57600080fd5b83356109aa816106f6565b925060208401356109ba816106f6565b915060408401356109ca816107b9565b809150509250925092565b6000602082840312156109e757600080fd5b8151610669816107b9565b600060208284031215610a0457600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610a4d57610a4d610a0b565b92915050565b80820180821115610a4d57610a4d610a0b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208284031215610aa757600080fd5b8135610669816106f6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610ae357610ae3610a0b565b5060010190565b6000825160005b81811015610b0b5760208186018101518583015201610af1565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Spardose.json b/crates/contracts/artifacts/Spardose.json new file mode 100644 index 0000000000..08787fb5a7 --- /dev/null +++ b/crates/contracts/artifacts/Spardose.json @@ -0,0 +1 @@ +{"abi":[{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ensureBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610364806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c56cca8314610030575b600080fd5b61004361003e366004610277565b610057565b604051901515815260200160405180910390f35b6040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015260009182918516906370a0823190602401602060405180830381865afa1580156100c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ec91906102b3565b90508281106100ff576001915050610133565b600061010b82856102cc565b905061012e73ffffffffffffffffffffffffffffffffffffffff8616878361013a565b925050505b9392505050565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790529151600092606091908716906101d3908490610306565b6000604051808303816000865af19150503d8060008114610210576040519150601f19603f3d011682016040523d82523d6000602084013e610215565b606091505b50909350905082801561012e575061012e8160008151600014806102485750818060200190518101906102489190610335565b92915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461027257600080fd5b919050565b60008060006060848603121561028c57600080fd5b6102958461024e565b92506102a36020850161024e565b9150604084013590509250925092565b6000602082840312156102c557600080fd5b5051919050565b81810381811115610248577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825160005b81811015610327576020818601810151858301520161030d565b506000920191825250919050565b60006020828403121561034757600080fd5b8151801515811461013357600080fdfea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c56cca8314610030575b600080fd5b61004361003e366004610277565b610057565b604051901515815260200160405180910390f35b6040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015260009182918516906370a0823190602401602060405180830381865afa1580156100c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ec91906102b3565b90508281106100ff576001915050610133565b600061010b82856102cc565b905061012e73ffffffffffffffffffffffffffffffffffffffff8616878361013a565b925050505b9392505050565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790529151600092606091908716906101d3908490610306565b6000604051808303816000865af19150503d8060008114610210576040519150601f19603f3d011682016040523d82523d6000602084013e610215565b606091505b50909350905082801561012e575061012e8160008151600014806102485750818060200190518101906102489190610335565b92915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461027257600080fd5b919050565b60008060006060848603121561028c57600080fd5b6102958461024e565b92506102a36020850161024e565b9150604084013590509250925092565b6000602082840312156102c557600080fd5b5051919050565b81810381811115610248577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825160005b81811015610327576020818601810151858301520161030d565b506000920191825250919050565b60006020828403121561034757600080fd5b8151801515811461013357600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Swapper.json b/crates/contracts/artifacts/Swapper.json index 3b4420dd93..7f6566ee0d 100644 --- a/crates/contracts/artifacts/Swapper.json +++ b/crates/contracts/artifacts/Swapper.json @@ -1 +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":{}}} +{"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":"0x608060405234801561001057600080fd5b506111d5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80631626ba7e1461003b5780639ed280dd146100a7575b600080fd5b610071610049366004610b16565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b6100ba6100b5366004610bcf565b6100c8565b60405190815260200161009e565b6000602085018035906100db9087610c5c565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610147573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016b9190610c79565b1015610179575060006108b8565b6102178673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101eb9190610c92565b60006101fa6020890189610c5c565b73ffffffffffffffffffffffffffffffffffffffff1691906108c1565b61029a8673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610265573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102899190610c92565b602087018035906101fa9089610c5c565b6040805160028082526060820183526000926020830190803683370190505090506102c86020870187610c5c565b816000815181106102db576102db610cde565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910182015261030b90860186610c5c565b8160018151811061031e5761031e610cde565b73ffffffffffffffffffffffffffffffffffffffff92909216602092830291909101820152604080516002808252606082018352600093919290918301908036833701905050905085602001358160008151811061037e5761037e610cde565b6020026020010181815250508660200135816001815181106103a2576103a2610cde565b6020908102919091010152604080516001808252818301909252600091816020015b6104406040518061016001604052806000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600063ffffffff16815260200160008019168152602001600081526020016000815260200160008152602001606081525090565b8152602001906001900390816103c45790505090506040518061016001604052806000815260200160018152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001896020013581526020018860200135815260200163ffffffff801681526020016000801b815260200160008152602001604081526020016000815260200130604051602001610506919060609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815260140190565b6040516020818303038152906040528152508160008151811061052b5761052b610cde565b602002602001018190525061053e610aef565b60208088013590610551908b018b610c5c565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e8c61057a60208c018c610c5c565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061060e9190610c79565b101561078c5760408051600180825281830190925290816020015b60408051606080820183526000808352602083015291810191909152815260200190600190039081610629575050815261066660208a018a610c5c565b8151805160009061067957610679610cde565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff9092169091526106ac908a018a610c5c565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b36106d460208a018a610c5c565b60405173ffffffffffffffffffffffffffffffffffffffff909116602482015260208a01356044820152606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b929092179091529050816000602002015160008151811061077c5761077c610cde565b6020026020010151604001819052505b60408051600180825281830190925290816020015b604080516060808201835260008083526020830152918101919091528152602001906001900390816107a157505060208201526107dd86610d85565b602082015180516000906107f3576107f3610cde565b60200260200101819052506108b18a73ffffffffffffffffffffffffffffffffffffffff166313d79a0b868686866040516024016108349493929190611084565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b9290921790915273ffffffffffffffffffffffffffffffffffffffff8d16915061099c565b9450505050505b95945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b30000000000000000000000000000000000000000000000000000000017905290600090610954908616836109b1565b9050610995816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506109bf565b5050505050565b60006109aa83600084610a0f565b9392505050565b60606109aa83600084610a42565b6109c882610ac8565b8190610a0a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a01919061113d565b60405180910390fd5b505050565b60005a905060008083516020850186885af1610a2f573d6000803e3d6000fd5b5a610a3a9082611150565b949350505050565b606060008473ffffffffffffffffffffffffffffffffffffffff168484604051610a6c919061118a565b60006040518083038185875af1925050503d8060008114610aa9576040519150601f19603f3d011682016040523d82523d6000602084013e610aae565b606091505b509250905080610ac057815160208301fd5b509392505050565b6000815160001480610ae9575081806020019051810190610ae991906111a6565b92915050565b60405180606001604052806003905b6060815260200190600190039081610afe5790505090565b600080600060408486031215610b2b57600080fd5b83359250602084013567ffffffffffffffff80821115610b4a57600080fd5b818601915086601f830112610b5e57600080fd5b813581811115610b6d57600080fd5b876020828501011115610b7f57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610bb457600080fd5b50565b600060408284031215610bc957600080fd5b50919050565b60008060008060006101008688031215610be857600080fd5b8535610bf381610b92565b9450610c028760208801610bb7565b9350610c118760608801610bb7565b9250610c208760a08801610bb7565b915060e086013567ffffffffffffffff811115610c3c57600080fd5b860160608189031215610c4e57600080fd5b809150509295509295909350565b600060208284031215610c6e57600080fd5b81356109aa81610b92565b600060208284031215610c8b57600080fd5b5051919050565b600060208284031215610ca457600080fd5b81516109aa81610b92565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6040516060810167ffffffffffffffff81118282101715610d3057610d30610caf565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d7d57610d7d610caf565b604052919050565b600060608236031215610d9757600080fd5b610d9f610d0d565b8235610daa81610b92565b815260208381013581830152604084013567ffffffffffffffff80821115610dd157600080fd5b9085019036601f830112610de457600080fd5b813581811115610df657610df6610caf565b610e26847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610d36565b91508082523684828501011115610e3c57600080fd5b808484018584013760009082019093019290925250604082015292915050565b60005b83811015610e77578181015183820152602001610e5f565b50506000910152565b60008151808452610e98816020860160208601610e5c565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015610faa5782840389528151610160815186528682015187870152604080830151610f2d8289018273ffffffffffffffffffffffffffffffffffffffff169052565b5050606082810151908701526080808301519087015260a08083015163ffffffff169087015260c0808301519087015260e080830151908701526101008083015190870152610120808301519087015261014091820151918601819052610f9681870183610e80565b9a87019a9550505090840190600101610ee8565b5091979650505050505050565b6000826060808201846000805b6003811015610faa578584038952825180518086526020918201918087019190600582901b88018101865b8381101561106d578982037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00185528551805173ffffffffffffffffffffffffffffffffffffffff16835283810151848401526040908101519083018c905261105a8c840182610e80565b9684019695840195925050600101610fef565b509c81019c97509590950194505050600101610fc4565b6080808252855190820181905260009060209060a0840190828901845b828110156110d357815173ffffffffffffffffffffffffffffffffffffffff16845292840192908401906001016110a1565b5050508381038285015286518082528783019183019060005b81811015611108578351835292840192918401916001016110ec565b5050848103604086015261111c8188610eca565b9250505082810360608401526111328185610fb7565b979650505050505050565b6020815260006109aa6020830184610e80565b81810381811115610ae9577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825161119c818460208701610e5c565b9190910192915050565b6000602082840312156111b857600080fd5b815180151581146109aa57600080fdfea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80631626ba7e1461003b5780639ed280dd146100a7575b600080fd5b610071610049366004610b16565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b6100ba6100b5366004610bcf565b6100c8565b60405190815260200161009e565b6000602085018035906100db9087610c5c565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610147573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016b9190610c79565b1015610179575060006108b8565b6102178673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101eb9190610c92565b60006101fa6020890189610c5c565b73ffffffffffffffffffffffffffffffffffffffff1691906108c1565b61029a8673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610265573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102899190610c92565b602087018035906101fa9089610c5c565b6040805160028082526060820183526000926020830190803683370190505090506102c86020870187610c5c565b816000815181106102db576102db610cde565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910182015261030b90860186610c5c565b8160018151811061031e5761031e610cde565b73ffffffffffffffffffffffffffffffffffffffff92909216602092830291909101820152604080516002808252606082018352600093919290918301908036833701905050905085602001358160008151811061037e5761037e610cde565b6020026020010181815250508660200135816001815181106103a2576103a2610cde565b6020908102919091010152604080516001808252818301909252600091816020015b6104406040518061016001604052806000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600063ffffffff16815260200160008019168152602001600081526020016000815260200160008152602001606081525090565b8152602001906001900390816103c45790505090506040518061016001604052806000815260200160018152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001896020013581526020018860200135815260200163ffffffff801681526020016000801b815260200160008152602001604081526020016000815260200130604051602001610506919060609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815260140190565b6040516020818303038152906040528152508160008151811061052b5761052b610cde565b602002602001018190525061053e610aef565b60208088013590610551908b018b610c5c565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e8c61057a60208c018c610c5c565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061060e9190610c79565b101561078c5760408051600180825281830190925290816020015b60408051606080820183526000808352602083015291810191909152815260200190600190039081610629575050815261066660208a018a610c5c565b8151805160009061067957610679610cde565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff9092169091526106ac908a018a610c5c565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b36106d460208a018a610c5c565b60405173ffffffffffffffffffffffffffffffffffffffff909116602482015260208a01356044820152606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b929092179091529050816000602002015160008151811061077c5761077c610cde565b6020026020010151604001819052505b60408051600180825281830190925290816020015b604080516060808201835260008083526020830152918101919091528152602001906001900390816107a157505060208201526107dd86610d85565b602082015180516000906107f3576107f3610cde565b60200260200101819052506108b18a73ffffffffffffffffffffffffffffffffffffffff166313d79a0b868686866040516024016108349493929190611084565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b9290921790915273ffffffffffffffffffffffffffffffffffffffff8d16915061099c565b9450505050505b95945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b30000000000000000000000000000000000000000000000000000000017905290600090610954908616836109b1565b9050610995816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506109bf565b5050505050565b60006109aa83600084610a0f565b9392505050565b60606109aa83600084610a42565b6109c882610ac8565b8190610a0a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a01919061113d565b60405180910390fd5b505050565b60005a905060008083516020850186885af1610a2f573d6000803e3d6000fd5b5a610a3a9082611150565b949350505050565b606060008473ffffffffffffffffffffffffffffffffffffffff168484604051610a6c919061118a565b60006040518083038185875af1925050503d8060008114610aa9576040519150601f19603f3d011682016040523d82523d6000602084013e610aae565b606091505b509250905080610ac057815160208301fd5b509392505050565b6000815160001480610ae9575081806020019051810190610ae991906111a6565b92915050565b60405180606001604052806003905b6060815260200190600190039081610afe5790505090565b600080600060408486031215610b2b57600080fd5b83359250602084013567ffffffffffffffff80821115610b4a57600080fd5b818601915086601f830112610b5e57600080fd5b813581811115610b6d57600080fd5b876020828501011115610b7f57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610bb457600080fd5b50565b600060408284031215610bc957600080fd5b50919050565b60008060008060006101008688031215610be857600080fd5b8535610bf381610b92565b9450610c028760208801610bb7565b9350610c118760608801610bb7565b9250610c208760a08801610bb7565b915060e086013567ffffffffffffffff811115610c3c57600080fd5b860160608189031215610c4e57600080fd5b809150509295509295909350565b600060208284031215610c6e57600080fd5b81356109aa81610b92565b600060208284031215610c8b57600080fd5b5051919050565b600060208284031215610ca457600080fd5b81516109aa81610b92565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6040516060810167ffffffffffffffff81118282101715610d3057610d30610caf565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d7d57610d7d610caf565b604052919050565b600060608236031215610d9757600080fd5b610d9f610d0d565b8235610daa81610b92565b815260208381013581830152604084013567ffffffffffffffff80821115610dd157600080fd5b9085019036601f830112610de457600080fd5b813581811115610df657610df6610caf565b610e26847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610d36565b91508082523684828501011115610e3c57600080fd5b808484018584013760009082019093019290925250604082015292915050565b60005b83811015610e77578181015183820152602001610e5f565b50506000910152565b60008151808452610e98816020860160208601610e5c565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015610faa5782840389528151610160815186528682015187870152604080830151610f2d8289018273ffffffffffffffffffffffffffffffffffffffff169052565b5050606082810151908701526080808301519087015260a08083015163ffffffff169087015260c0808301519087015260e080830151908701526101008083015190870152610120808301519087015261014091820151918601819052610f9681870183610e80565b9a87019a9550505090840190600101610ee8565b5091979650505050505050565b6000826060808201846000805b6003811015610faa578584038952825180518086526020918201918087019190600582901b88018101865b8381101561106d578982037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00185528551805173ffffffffffffffffffffffffffffffffffffffff16835283810151848401526040908101519083018c905261105a8c840182610e80565b9684019695840195925050600101610fef565b509c81019c97509590950194505050600101610fc4565b6080808252855190820181905260009060209060a0840190828901845b828110156110d357815173ffffffffffffffffffffffffffffffffffffffff16845292840192908401906001016110a1565b5050508381038285015286518082528783019183019060005b81811015611108578351835292840192918401916001016110ec565b5050848103604086015261111c8188610eca565b9250505082810360608401526111328185610fb7565b979650505050505050565b6020815260006109aa6020830184610e80565b81810381811115610ae9577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825161119c818460208701610e5c565b9190910192915050565b6000602082840312156111b857600080fd5b815180151581146109aa57600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Trader.json b/crates/contracts/artifacts/Trader.json index f2a600e845..2614e657ac 100644 --- a/crates/contracts/artifacts/Trader.json +++ b/crates/contracts/artifacts/Trader.json @@ -1 +1 @@ -{"abi":[{"stateMutability":"payable","type":"fallback"},{"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":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610bb5806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a136600461098b565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a2c565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610aa3565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61077f565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610abf565b9050828110156103e55760006103168285610ad8565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104759190610b18565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105099190610abf565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105859190610b18565b73ffffffffffffffffffffffffffffffffffffffff86169060006107ba565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106169190610b18565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6107ba565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156106c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e59190610abf565b905083811015610777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b505050505050565b6000806107ad60017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610ad8565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b3000000000000000000000000000000000000000000000000000000001790529060009061084d90861683610895565b905061088e816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506108aa565b5050505050565b60606108a383600084610905565b9392505050565b815115806108c75750818060200190518101906108c79190610b35565b8190610900576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610b57565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161092f9190610aa3565b60006040518083038185875af1925050503d806000811461096c576040519150601f19603f3d011682016040523d82523d6000602084013e610971565b606091505b50925090508061098357815160208301fd5b509392505050565b6000806000604084860312156109a057600080fd5b83359250602084013567ffffffffffffffff808211156109bf57600080fd5b818601915086601f8301126109d357600080fd5b8135818111156109e257600080fd5b8760208285010111156109f457600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a2957600080fd5b50565b60008060008060808587031215610a4257600080fd5b8435610a4d81610a07565b93506020850135610a5d81610a07565b9250604085013591506060850135610a7481610a07565b939692955090935050565b60005b83811015610a9a578181015183820152602001610a82565b50506000910152565b60008251610ab5818460208701610a7f565b9190910192915050565b600060208284031215610ad157600080fd5b5051919050565b81810381811115610b12577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610b2a57600080fd5b81516108a381610a07565b600060208284031215610b4757600080fd5b815180151581146108a357600080fd5b6020815260008251806020840152610b76816040850160208701610a7f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a136600461098b565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a2c565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610aa3565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61077f565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610abf565b9050828110156103e55760006103168285610ad8565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104759190610b18565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105099190610abf565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105859190610b18565b73ffffffffffffffffffffffffffffffffffffffff86169060006107ba565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106169190610b18565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6107ba565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156106c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e59190610abf565b905083811015610777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b505050505050565b6000806107ad60017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610ad8565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b3000000000000000000000000000000000000000000000000000000001790529060009061084d90861683610895565b905061088e816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506108aa565b5050505050565b60606108a383600084610905565b9392505050565b815115806108c75750818060200190518101906108c79190610b35565b8190610900576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610b57565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161092f9190610aa3565b60006040518083038185875af1925050503d806000811461096c576040519150601f19603f3d011682016040523d82523d6000602084013e610971565b606091505b50925090508061098357815160208301fd5b509392505050565b6000806000604084860312156109a057600080fd5b83359250602084013567ffffffffffffffff808211156109bf57600080fd5b818601915086601f8301126109d357600080fd5b8135818111156109e257600080fd5b8760208285010111156109f457600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a2957600080fd5b50565b60008060008060808587031215610a4257600080fd5b8435610a4d81610a07565b93506020850135610a5d81610a07565b9250604085013591506060850135610a7481610a07565b939692955090935050565b60005b83811015610a9a578181015183820152602001610a82565b50506000910152565b60008251610ab5818460208701610a7f565b9190910192915050565b600060208284031215610ad157600080fd5b5051919050565b81810381811115610b12577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610b2a57600080fd5b81516108a381610a07565b600060208284031215610b4757600080fd5b815180151581146108a357600080fd5b6020815260008251806020840152610b76816040850160208701610a7f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"stateMutability":"payable","type":"fallback"},{"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":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610a97806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610873565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610914565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff1683604051610149919061098b565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61065b565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061030091906109a7565b9050828110156103e557600061031682856109c0565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061047591906109fa565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061050991906109a7565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058591906109fa565b73ffffffffffffffffffffffffffffffffffffffff8616906000610696565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061691906109fa565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610696565b5050505050565b60008061068960017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c97356109c0565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906107299086168361076a565b9050610654816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c656400000000000081525061077f565b6060610778836000846107c6565b9392505050565b6107888261084c565b81906107c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610a17565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516107f0919061098b565b60006040518083038185875af1925050503d806000811461082d576040519150601f19603f3d011682016040523d82523d6000602084013e610832565b606091505b50925090508061084457815160208301fd5b509392505050565b600081516000148061086d57508180602001905181019061086d9190610a68565b92915050565b60008060006040848603121561088857600080fd5b83359250602084013567ffffffffffffffff808211156108a757600080fd5b818601915086601f8301126108bb57600080fd5b8135818111156108ca57600080fd5b8760208285010111156108dc57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff8116811461091157600080fd5b50565b6000806000806080858703121561092a57600080fd5b8435610935816108ef565b93506020850135610945816108ef565b925060408501359150606085013561095c816108ef565b939692955090935050565b60005b8381101561098257818101518382015260200161096a565b50506000910152565b6000825161099d818460208701610967565b9190910192915050565b6000602082840312156109b957600080fd5b5051919050565b8181038181111561086d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610a0c57600080fd5b8151610778816108ef565b6020815260008251806020840152610a36816040850160208701610967565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b600060208284031215610a7a57600080fd5b8151801515811461077857600080fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610873565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610914565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff1683604051610149919061098b565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61065b565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061030091906109a7565b9050828110156103e557600061031682856109c0565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061047591906109fa565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061050991906109a7565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058591906109fa565b73ffffffffffffffffffffffffffffffffffffffff8616906000610696565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061691906109fa565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610696565b5050505050565b60008061068960017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c97356109c0565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906107299086168361076a565b9050610654816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c656400000000000081525061077f565b6060610778836000846107c6565b9392505050565b6107888261084c565b81906107c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610a17565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516107f0919061098b565b60006040518083038185875af1925050503d806000811461082d576040519150601f19603f3d011682016040523d82523d6000602084013e610832565b606091505b50925090508061084457815160208301fd5b509392505050565b600081516000148061086d57508180602001905181019061086d9190610a68565b92915050565b60008060006040848603121561088857600080fd5b83359250602084013567ffffffffffffffff808211156108a757600080fd5b818601915086601f8301126108bb57600080fd5b8135818111156108ca57600080fd5b8760208285010111156108dc57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff8116811461091157600080fd5b50565b6000806000806080858703121561092a57600080fd5b8435610935816108ef565b93506020850135610945816108ef565b925060408501359150606085013561095c816108ef565b939692955090935050565b60005b8381101561098257818101518382015260200161096a565b50506000910152565b6000825161099d818460208701610967565b9190910192915050565b6000602082840312156109b957600080fd5b5051919050565b8181038181111561086d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610a0c57600080fd5b8151610778816108ef565b6020815260008251806020840152610a36816040850160208701610967565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b600060208284031215610a7a57600080fd5b8151801515811461077857600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 7a6e4369bb..dda30d09e8 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -990,8 +990,9 @@ fn main() { generate_contract("CowAmmUniswapV2PriceOracle"); // Support contracts used for trade and token simulations. - generate_contract("Trader"); generate_contract("Solver"); + generate_contract("Spardose"); + generate_contract("Trader"); // Support contracts used for various order simulations. generate_contract("Balances"); diff --git a/crates/contracts/solidity/Makefile b/crates/contracts/solidity/Makefile index 5816391d8d..9402416722 100644 --- a/crates/contracts/solidity/Makefile +++ b/crates/contracts/solidity/Makefile @@ -14,6 +14,7 @@ CONTRACTS := \ Signatures.sol \ SimulateCode.sol \ Solver.sol \ + Spardose.sol \ Swapper.sol \ Trader.sol ARTIFACTS := $(patsubst %.sol,$(ARTIFACTDIR)/%.json,$(CONTRACTS)) diff --git a/crates/contracts/solidity/Solver.sol b/crates/contracts/solidity/Solver.sol index 9833c1e5c1..eece404458 100644 --- a/crates/contracts/solidity/Solver.sol +++ b/crates/contracts/solidity/Solver.sol @@ -6,6 +6,7 @@ import { Interaction, Trade, ISettlement } from "./interfaces/ISettlement.sol"; import { Caller } from "./libraries/Caller.sol"; import { Math } from "./libraries/Math.sol"; import { SafeERC20 } from "./libraries/SafeERC20.sol"; +import { Spardose } from "./Spardose.sol"; import { Trader } from "./Trader.sol"; /// @title A contract for impersonating a solver. This contract @@ -16,7 +17,11 @@ import { Trader } from "./Trader.sol"; contract Solver { using Caller for *; using Math for *; - using SafeERC20 for *; + + struct Mock { + bool enabled; + address spardose; + } uint256 private _simulationOverhead; uint256[] private _queriedBalances; @@ -35,10 +40,10 @@ contract Solver { /// @param tokens - list of tokens used in the trade /// @param receiver - address receiving the bought tokens /// @param settlementCall - the calldata of the `settle()` call - /// @param mockPreconditions - controls whether things like ETH wrapping - /// or setting allowance should be done on behalf of the - /// user to support quote verification even if the user didn't - /// wrap their ETH or set the necessary allowances yet. + /// @param mock - mocking configuration for the simulation; this controls + /// whether things like ETH wrapping, setting allowance and + /// pre-funding should be done on behalf of the user to support + /// quote verification for users who aren't ready to swap. /// /// @return gasUsed - gas used for the `settle()` call /// @return queriedBalances - list of balances stored during the simulation @@ -51,16 +56,16 @@ contract Solver { address[] calldata tokens, address payable receiver, bytes calldata settlementCall, - bool mockPreconditions + Mock memory mock ) external returns ( uint256 gasUsed, uint256[] memory queriedBalances ) { require(msg.sender == address(this), "only simulation logic is allowed to call 'swap' function"); - // Prepare the trade in the context of the trader so we are allowed - // to set approvals and things like that. - if (mockPreconditions) { + if (mock.enabled) { + // Prepare the trade in the context of the trader so we are allowed + // to set approvals and things like that. Trader(trader) .prepareSwap( settlementContract, @@ -68,13 +73,24 @@ contract Solver { sellAmount, nativeToken ); + + // Ensure that the user has sufficient sell token balance. In case + // balance overrides are enabled, the Spardose will fund the trader + // with simulated balances. + require( + Spardose(mock.spardose).ensureBalance(trader, sellToken, sellAmount), + "trader does not have enough sell token" + ); } // Warm the storage for sending ETH to smart contract addresses. // We allow this call to revert becaues it was either unnecessary in the first place // or failing to send `ETH` to the `receiver` will cause a revert in the settlement // contract. - receiver.call{value: 0}(""); + { + (bool success,) = receiver.call{value: 0}(""); + success; + } // Store pre-settlement balances _storeSettlementBalances(tokens, settlementContract); diff --git a/crates/contracts/solidity/Spardose.sol b/crates/contracts/solidity/Spardose.sol new file mode 100644 index 0000000000..c1fa6167be --- /dev/null +++ b/crates/contracts/solidity/Spardose.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { IERC20 } from "./interfaces/IERC20.sol"; +import { SafeERC20 } from "./libraries/SafeERC20.sol"; + +/// @title A piggy bank contract (Spardose is piggy bank in German) +/// @notice This contract account is used for pre-funding traders with tokens +/// for quote simulations. A separate contract is used (instead of overriding +/// the balance of the solver or trader directly) in order to interfere as +/// little as possible with the settlement. +contract Spardose { + using SafeERC20 for *; + + /// @dev Ensures that the trader has at least `amount` tokens. If not, it + /// will try and transfer the difference to the trader. + /// + /// @param trader - the address of the trader + /// @param token - the token to ensure a balance for + /// @param amount - the amount of `token` that the `trader` must hold + /// + /// @return success - the `trader`'s `token` balance is more than `amount` + function ensureBalance(address trader, address token, uint256 amount) external returns (bool success) { + uint256 traderBalance = IERC20(token).balanceOf(trader); + if (traderBalance >= amount) { + return true; + } + + uint256 difference = amount - traderBalance; + return IERC20(token).trySafeTransfer(trader, difference); + } +} diff --git a/crates/contracts/solidity/Trader.sol b/crates/contracts/solidity/Trader.sol index d1354cb982..c9fa5e0fdf 100644 --- a/crates/contracts/solidity/Trader.sol +++ b/crates/contracts/solidity/Trader.sol @@ -99,9 +99,6 @@ contract Trader { IERC20(sellToken).safeApprove(address(settlementContract.vaultRelayer()), 0); IERC20(sellToken).safeApprove(address(settlementContract.vaultRelayer()), type(uint256).max); } - - uint256 availableSellToken = IERC20(sellToken).balanceOf(address(this)); - require(availableSellToken >= sellAmount, "trader does not have enough sell_token"); } /// @dev Validate all signature requests. This makes "signing" CoW protocol diff --git a/crates/contracts/solidity/libraries/SafeERC20.sol b/crates/contracts/solidity/libraries/SafeERC20.sol index 785ff3324d..9cc30ef35f 100644 --- a/crates/contracts/solidity/libraries/SafeERC20.sol +++ b/crates/contracts/solidity/libraries/SafeERC20.sol @@ -7,13 +7,24 @@ import { Caller } from "./Caller.sol"; library SafeERC20 { using Caller for *; + function trySafeTransfer(IERC20 self, address target, uint256 amount) internal returns (bool success) { + bytes memory cdata = abi.encodeCall(self.transfer, (target, amount)); + bytes memory rdata; + (success, rdata) = address(self).call(cdata); + return success && check(rdata); + } + function safeApprove(IERC20 self, address target, uint256 amount) internal { bytes memory cdata = abi.encodeCall(self.approve, (target, amount)); bytes memory rdata = address(self).doCall(cdata); - check(rdata, "SafeERC20: approval failed"); + ensure(rdata, "SafeERC20: approval failed"); + } + + function check(bytes memory rdata) internal pure returns (bool ok) { + return rdata.length == 0 || abi.decode(rdata, (bool)); } - function check(bytes memory self, string memory message) internal pure { - require(self.length == 0 || abi.decode(self, (bool)), message); + function ensure(bytes memory rdata, string memory message) internal pure { + require(check(rdata), message); } } diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index b033656adb..f9aefaca3e 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -114,6 +114,7 @@ pub mod support { Signatures; SimulateCode; Solver; + Spardose; Swapper; Trader; } diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 5e442d2e35..2aafb0e98b 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -1,7 +1,7 @@ use { bigdecimal::{BigDecimal, Zero}, - e2e::setup::*, - ethcontract::H160, + e2e::{setup::*, tx}, + ethcontract::{H160, U256}, ethrpc::Web3, model::{ order::{BuyTokenDestination, OrderKind, SellTokenSource}, @@ -19,6 +19,12 @@ use { std::{str::FromStr, sync::Arc}, }; +#[tokio::test] +#[ignore] +async fn local_node_standard_verified_quote() { + run_test(standard_verified_quote).await; +} + #[tokio::test] #[ignore] async fn forked_node_bypass_verification_for_rfq_quotes() { @@ -37,6 +43,51 @@ async fn local_node_verified_quote_for_settlement_contract() { run_test(verified_quote_for_settlement_contract).await; } +#[tokio::test] +#[ignore] +async fn local_node_verified_quote_with_simulated_balance() { + run_test(verified_quote_with_simulated_balance).await; +} + +/// Verified quotes work as expected. +async fn standard_verified_quote(web3: Web3) { + tracing::info!("Setting up chain state."); + let mut onchain = OnchainComponents::deploy(web3).await; + + let [solver] = onchain.make_solvers(to_wei(10)).await; + let [trader] = onchain.make_accounts(to_wei(1)).await; + let [token] = onchain + .deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000)) + .await; + + token.mint(trader.address(), to_wei(1)).await; + tx!( + trader.account(), + token.approve(onchain.contracts().allowance, to_wei(1)) + ); + + tracing::info!("Starting services."); + let services = Services::new(&onchain).await; + services.start_protocol(solver).await; + + // quote where the trader has no balances or approval set. + let response = services + .submit_quote(&OrderQuoteRequest { + from: trader.address(), + sell_token: token.address(), + buy_token: onchain.contracts().weth.address(), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::BeforeFee { + value: to_wei(1).try_into().unwrap(), + }, + }, + ..Default::default() + }) + .await + .unwrap(); + assert!(response.verified); +} + /// The block number from which we will fetch state for the forked tests. const FORK_BLOCK_MAINNET: u64 = 19796077; @@ -208,3 +259,50 @@ async fn verified_quote_for_settlement_contract(web3: Web3) { .unwrap(); assert!(response.verified); } + +/// Test that asserts that we can verify quotes for traders with simulated +/// balances. +async fn verified_quote_with_simulated_balance(web3: Web3) { + tracing::info!("Setting up chain state."); + let mut onchain = OnchainComponents::deploy(web3).await; + + let [solver] = onchain.make_solvers(to_wei(10)).await; + let [trader] = onchain.make_accounts(to_wei(0)).await; + let [token] = onchain + .deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000)) + .await; + + tracing::info!("Starting services."); + let services = Services::new(&onchain).await; + services + .start_protocol_with_args( + ExtraServiceArgs { + api: vec![format!( + // The OpenZeppelin `ERC20Mintable` token uses a mapping in + // the first (0'th) storage slot for balances. + "--quote-token-balance-overrides={:?}@0", + token.address() + )], + ..Default::default() + }, + solver, + ) + .await; + + // quote where the trader has no balances or approval set. + let response = services + .submit_quote(&OrderQuoteRequest { + from: trader.address(), + sell_token: token.address(), + buy_token: onchain.contracts().weth.address(), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::BeforeFee { + value: to_wei(1).try_into().unwrap(), + }, + }, + ..Default::default() + }) + .await + .unwrap(); + assert!(response.verified); +} diff --git a/crates/ethrpc/src/extensions.rs b/crates/ethrpc/src/extensions.rs index 78f2d77eea..d57ff77993 100644 --- a/crates/ethrpc/src/extensions.rs +++ b/crates/ethrpc/src/extensions.rs @@ -68,12 +68,12 @@ pub struct StateOverride { /// Fake key-value mapping to override **all** slots in the account storage /// before executing the call. #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option>, + pub state: Option>, /// Fake key-value mapping to override **individual** slots in the account /// storage before executing the call. #[serde(skip_serializing_if = "Option::is_none")] - pub state_diff: Option>, + pub state_diff: Option>, } #[cfg(test)] diff --git a/crates/shared/src/code_simulation.rs b/crates/shared/src/code_simulation.rs index 7035c2dfb6..40a3fb4d82 100644 --- a/crates/shared/src/code_simulation.rs +++ b/crates/shared/src/code_simulation.rs @@ -4,7 +4,7 @@ use { crate::tenderly_api::{SimulationKind, SimulationRequest, StateObject, TenderlyApi}, anyhow::{ensure, Context as _, Result}, contracts::errors::EthcontractErrorType, - ethcontract::{errors::ExecutionError, H256}, + ethcontract::errors::ExecutionError, ethrpc::{ extensions::{EthExt as _, StateOverride, StateOverrides}, Web3, @@ -206,16 +206,7 @@ impl TryFrom for StateObject { Ok(StateObject { balance: value.balance, code: value.code, - storage: value.state_diff.map(|state_diff| { - state_diff - .into_iter() - .map(|(key, uint)| { - let mut value = H256::default(); - uint.to_big_endian(&mut value.0); - (key, value) - }) - .collect() - }), + storage: value.state_diff, }) } } @@ -262,6 +253,7 @@ mod tests { use { super::*, crate::{ethrpc::create_env_test_transport, tenderly_api::TenderlyHttpApi}, + ethcontract::H256, hex_literal::hex, maplit::hashmap, std::time::Duration, @@ -393,7 +385,8 @@ mod tests { hashmap! { addr!("D533a949740bb3306d119CC777fa900bA034cd52") => StateOverride { state_diff: Some(hashmap! { - H256(balance_slot) => 1.into() + H256(balance_slot) => + H256(hex!("0000000000000000000000000000000000000000000000000000000000000001")), }), ..Default::default() }, diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 03b2083264..2d660adf03 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -113,18 +113,21 @@ impl<'a> PriceEstimatorFactory<'a> { None => Arc::new(web3.clone()), }; - Ok(Some(Arc::new( - TradeVerifier::new( - web3, - simulator, - components.code_fetcher.clone(), - network.block_stream.clone(), - network.settlement, - network.native_token, - args.quote_inaccuracy_limit.clone(), - ) - .await?, - ))) + let balance_overrides = Arc::new(args.quote_token_balance_overrides.clone()); + + let verifier = TradeVerifier::new( + web3, + simulator, + components.code_fetcher.clone(), + network.block_stream.clone(), + network.settlement, + network.native_token, + args.quote_inaccuracy_limit.clone(), + ) + .await? + .with_balance_overrides(balance_overrides); + + Ok(Some(Arc::new(verifier))) } fn native_token_price_estimation_amount(&self) -> Result { diff --git a/crates/shared/src/price_estimation/mod.rs b/crates/shared/src/price_estimation/mod.rs index 9ba6405f9a..607925bc83 100644 --- a/crates/shared/src/price_estimation/mod.rs +++ b/crates/shared/src/price_estimation/mod.rs @@ -1,4 +1,5 @@ use { + self::trade_verifier::balance_overrides::ConfigurationBalanceOverrides, crate::{ arguments::{display_option, display_secret_option, ExternalSolver}, trade_finding::Interaction, @@ -211,6 +212,17 @@ pub struct Arguments { value_parser = humantime::parse_duration, )] pub quote_timeout: Duration, + + /// Token configuration for simulated balances on verified quotes. This + /// allows the quote verification system to produce verified quotes for + /// traders without sufficient balance for the configured token pairs. + /// + /// The expected format is a comma separated list of `${ADDR}@${SLOT}`, + /// where `ADDR` is the token address and `SLOT` is the Solidity storage + /// slot for the balances mapping. For example for WETH: + /// `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2@3`. + #[clap(long, env, default_value_t)] + pub quote_token_balance_overrides: ConfigurationBalanceOverrides, } #[derive(clap::Parser)] @@ -290,6 +302,7 @@ impl Display for Arguments { quote_inaccuracy_limit, quote_verification, quote_timeout, + quote_token_balance_overrides, } = self; display_option( @@ -367,6 +380,11 @@ impl Display for Arguments { writeln!(f, "quote_inaccuracy_limit: {}", quote_inaccuracy_limit)?; writeln!(f, "quote_verification: {:?}", quote_verification)?; writeln!(f, "quote_timeout: {:?}", quote_timeout)?; + writeln!( + f, + "quote_token_balance_overrides: {:?}", + quote_token_balance_overrides + )?; Ok(()) } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 7107ac5ff0..fed26bb928 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -1,6 +1,11 @@ pub mod balance_overrides; use { + self::balance_overrides::{ + BalanceOverrideRequest, + BalanceOverriding, + ConfigurationBalanceOverrides, + }, super::{Estimate, Verification}, crate::{ code_fetching::CodeFetching, @@ -18,7 +23,7 @@ use { contracts::{ deployed_bytecode, dummy_contract, - support::{AnyoneAuthenticator, Solver, Trader}, + support::{AnyoneAuthenticator, Solver, Spardose, Trader}, GPv2Settlement, WETH9, }, @@ -58,6 +63,7 @@ pub struct TradeVerifier { web3: Web3, simulator: Arc, code_fetcher: Arc, + balance_overrides: Arc, block_stream: CurrentBlockWatcher, settlement: GPv2Settlement, native_token: H160, @@ -67,6 +73,7 @@ pub struct TradeVerifier { impl TradeVerifier { const DEFAULT_GAS: u64 = 8_000_000; + const SPARDOSE: H160 = addr!("0000000000000000000000000000000000020000"); const TRADER_IMPL: H160 = addr!("0000000000000000000000000000000000010000"); pub async fn new( @@ -84,6 +91,7 @@ impl TradeVerifier { Ok(Self { simulator, code_fetcher, + balance_overrides: Arc::new(ConfigurationBalanceOverrides::default()), block_stream, settlement: settlement_contract, native_token, @@ -93,6 +101,11 @@ impl TradeVerifier { }) } + pub fn with_balance_overrides(mut self, balance_overrides: Arc) -> Self { + self.balance_overrides = balance_overrides; + self + } + async fn verify_inner( &self, query: &PriceQuery, @@ -153,6 +166,11 @@ impl TradeVerifier { OrderKind::Buy => *out_amount, }; + // Only enable additional mocking (approvals, native token wrapping, + // balance overrides) if the user did not provide pre-interactions. If + // the user did provide pre-interactions, it's reasonable to assume that + // they will set up all the necessary details of the trade. + let mock_enabled = verification.pre_interactions.is_empty(); let simulation = solver .methods() .swap( @@ -164,11 +182,7 @@ impl TradeVerifier { tokens.clone(), verification.receiver, Bytes(settlement.data.unwrap().0), - // only if the user did not provide pre-interactions is it safe - // to set up the trade's pre-conditions on behalf of the user. - // if the user provided pre-interactions it's reasonable to assume - // that they will set up all the necessary details for the trade. - verification.pre_interactions.is_empty(), + (mock_enabled, Self::SPARDOSE), ) .tx; @@ -185,7 +199,7 @@ impl TradeVerifier { }; let overrides = self - .prepare_state_overrides(verification, trade) + .prepare_state_overrides(verification, query, trade) .await .map_err(Error::SimulationFailed)?; @@ -281,6 +295,7 @@ impl TradeVerifier { async fn prepare_state_overrides( &self, verification: &Verification, + query: &PriceQuery, trade: &TradeKind, ) -> Result> { // Set up mocked trader. @@ -308,6 +323,47 @@ impl TradeVerifier { ); } + // Setup the funding contract override. Regardless of whether or not the + // contract has funds, it needs to exist in order to not revert + // simulations (Solidity reverts on attempts to call addresses without + // any code). + overrides.insert( + Self::SPARDOSE, + StateOverride { + code: Some(deployed_bytecode!(Spardose)), + ..Default::default() + }, + ); + + // Provide mocked balances if possible to the spardose to allow it to + // give some balances to the trader in order to verify trades even for + // owners without balances. Note that we use a separate account for + // funding to not interfere with the settlement process. This allows the + // simulation to conditionally transfer the balance only when it is + // safe to mock the trade pre-conditions on behalf of the user and to + // not alter solver balances which may be used during settlement. We use + // a similar strategy for determining whether or not to set approvals on + // behalf of the trader. + if let Some(solver_balance_override) = + self.balance_overrides + .state_override(&BalanceOverrideRequest { + token: query.sell_token, + holder: Self::SPARDOSE, + amount: match query.kind { + OrderKind::Sell => query.in_amount.get(), + OrderKind::Buy => trade.out_amount( + &query.buy_token, + &query.sell_token, + &query.in_amount.get(), + &query.kind, + )?, + }, + }) + { + tracing::debug!(?solver_balance_override, "solver balance override enabled"); + overrides.insert(query.sell_token, solver_balance_override); + } + // Set up mocked solver. let mut solver_override = StateOverride { code: Some(deployed_bytecode!(Solver)), diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs index e8992efb6c..65e04f0665 100644 --- a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs @@ -1,8 +1,13 @@ use { + anyhow::Context as _, ethcontract::{Address, H256, U256}, ethrpc::extensions::StateOverride, maplit::hashmap, - std::collections::HashMap, + std::{ + collections::HashMap, + fmt::{self, Display, Formatter}, + str::FromStr, + }, web3::signing, }; @@ -10,7 +15,7 @@ use { /// /// This allows a wider range of verified quotes to work, even when balances /// are not available for the quoter. -pub trait BalanceOverriding { +pub trait BalanceOverriding: Send + Sync + 'static { fn state_override(&self, request: &BalanceOverrideRequest) -> Option; } @@ -43,13 +48,68 @@ impl BalanceOverriding for ConfigurationBalanceOverrides { fn state_override(&self, request: &BalanceOverrideRequest) -> Option { let strategy = self.0.get(&request.token)?; match strategy { - Strategy::Mapping { slot } => Some(StateOverride { - state_diff: Some(hashmap! { - address_mapping_storage_slot(slot, &request.holder) => request.amount, - }), - ..Default::default() - }), + Strategy::Mapping { slot } => { + let slot = address_mapping_storage_slot(slot, &request.holder); + let value = { + let mut value = H256::default(); + request.amount.to_big_endian(&mut value.0); + value + }; + Some(StateOverride { + state_diff: Some(hashmap! { slot => value }), + ..Default::default() + }) + } + } + } +} + +impl Display for ConfigurationBalanceOverrides { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let format_entry = + |f: &mut Formatter, (addr, strategy): (&Address, &Strategy)| match strategy { + Strategy::Mapping { slot } => write!(f, "{addr:?}@{slot}"), + }; + + let mut entries = self.0.iter(); + + let Some(first) = entries.next() else { + return Ok(()); + }; + format_entry(f, first)?; + + for entry in entries { + f.write_str(",")?; + format_entry(f, entry)?; } + + Ok(()) + } +} + +impl FromStr for ConfigurationBalanceOverrides { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Ok(Self::default()); + } + + let entries = s + .split(',') + .map(|part| -> Result<_, Self::Err> { + let (addr, slot) = part + .split_once('@') + .context("expected {addr}@{slot} format")?; + Ok(( + addr.parse()?, + Strategy::Mapping { + slot: slot.parse()?, + }, + )) + }) + .collect::>()?; + Ok(Self(entries)) } } @@ -84,7 +144,8 @@ mod tests { }), Some(StateOverride { state_diff: Some(hashmap! { - H256(hex!("fca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33")) => 0x42_u64.into() + H256(hex!("fca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33")) => + H256(hex!("0000000000000000000000000000000000000000000000000000000000000042")), }), ..Default::default() }), From a88bfbd4cfcb3981188bcd3090cc7f66607ffdcc Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Fri, 6 Dec 2024 10:02:28 +0100 Subject: [PATCH 06/25] Add A Balance Override Detector (#3148) # Description This PR adds a balance override detector for quote verification. This allows tokens to automatically support balance overrides without manual configurations. # Changes A new (but as of yet) unused component is added that can detect balance override strategies for tokens. The current implementation is quite simple - just try a bunch of different slots and see which one works. For now, the number of slots to try is hard-coded at 25, but this can trivially be changed to be configurable in the future. The component is marked with `#[allow(dead_code)]` as it isn't being used anywhere, In the interest of keeping the PR size down, I will add all of the glue code to make it used in a follow up (which will also test whether or not the detector works as expected). ## How to Test In the meantime, I did some local testing to check that it works with tokens for which I know the mapping slot on Ethereum Mainnet (test not included in the PR): ```rust async fn temporary_test() { let web3 = Arc::new(ethrpc::Web3::new(ethrpc::create_test_transport( "https://eth.llamarpc.com", ))); let detector = Detector::new(web3); let result = detector .detect(addr!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")) .await; println!("{result:?}"); assert!(matches!(result, Ok(Strategy::Mapping { slot }) if slot == U256::from(9_u8))); } ``` This test successfully passes! --- .../trade_verifier/balance_overrides.rs | 63 ++++++----- .../balance_overrides/detector.rs | 102 ++++++++++++++++++ 2 files changed, 140 insertions(+), 25 deletions(-) create mode 100644 crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs index 65e04f0665..ae7550f9a2 100644 --- a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs @@ -1,3 +1,5 @@ +mod detector; + use { anyhow::Context as _, ethcontract::{Address, H256, U256}, @@ -33,11 +35,42 @@ pub struct BalanceOverrideRequest { #[derive(Clone, Debug, Default)] pub struct ConfigurationBalanceOverrides(HashMap); +/// Balance override strategy for a token. #[derive(Clone, Debug)] pub enum Strategy { + /// Balance override strategy for tokens whose balances are stored in a + /// direct Solidity mapping from token holder to balance amount in the + /// form `mapping(address holder => uint256 amount)`. + /// + /// The strategy is configured with the storage slot [^1] of the mapping. + /// + /// [^1]: Mapping { slot: U256 }, } +impl Strategy { + /// Computes the storage slot and value to override for a particular token + /// holder and amount. + fn state_override(&self, holder: &Address, amount: &U256) -> (H256, H256) { + match self { + Self::Mapping { slot } => { + let key = { + let mut buf = [0; 64]; + buf[12..32].copy_from_slice(holder.as_fixed_bytes()); + slot.to_big_endian(&mut buf[32..64]); + H256(signing::keccak256(&buf)) + }; + let value = { + let mut buf = [0; 32]; + amount.to_big_endian(&mut buf); + H256(buf) + }; + (key, value) + } + } + } +} + impl ConfigurationBalanceOverrides { pub fn new(config: HashMap) -> Self { Self(config) @@ -47,20 +80,11 @@ impl ConfigurationBalanceOverrides { impl BalanceOverriding for ConfigurationBalanceOverrides { fn state_override(&self, request: &BalanceOverrideRequest) -> Option { let strategy = self.0.get(&request.token)?; - match strategy { - Strategy::Mapping { slot } => { - let slot = address_mapping_storage_slot(slot, &request.holder); - let value = { - let mut value = H256::default(); - request.amount.to_big_endian(&mut value.0); - value - }; - Some(StateOverride { - state_diff: Some(hashmap! { slot => value }), - ..Default::default() - }) - } - } + let (key, value) = strategy.state_override(&request.holder, &request.amount); + Some(StateOverride { + state_diff: Some(hashmap! { key => value }), + ..Default::default() + }) } } @@ -113,17 +137,6 @@ impl FromStr for ConfigurationBalanceOverrides { } } -/// Computes the storage slot where the value is stored for Solidity mappings -/// of the form `mapping(address => ...)`. -/// -/// See . -fn address_mapping_storage_slot(slot: &U256, address: &Address) -> H256 { - let mut buf = [0; 64]; - buf[12..32].copy_from_slice(address.as_fixed_bytes()); - slot.to_big_endian(&mut buf[32..64]); - H256(signing::keccak256(&buf)) -} - #[cfg(test)] mod tests { use {super::*, hex_literal::hex}; diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs new file mode 100644 index 0000000000..0ce48951e3 --- /dev/null +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs @@ -0,0 +1,102 @@ +use { + super::Strategy, + crate::code_simulation::{CodeSimulating, SimulationError}, + contracts::{dummy_contract, ERC20}, + ethcontract::{Address, U256}, + ethrpc::extensions::StateOverride, + maplit::hashmap, + std::sync::Arc, + thiserror::Error, + web3::{signing::keccak256, types::CallRequest}, +}; + +/// A heuristic balance override detector based on `eth_call` simulations. +/// +/// This has the exact same node requirements as trade verification. +pub struct Detector { + simulator: Arc, +} + +#[allow(dead_code)] +impl Detector { + /// Number of different slots to try out. + const TRIES: u8 = 25; + + /// Creates a new balance override detector. + pub fn new(simulator: Arc) -> Self { + Self { simulator } + } + + /// Tries to detect the balance override strategy for the specified token. + /// Returns an `Err` if it cannot detect the strategy or an internal + /// simulation fails. + pub async fn detect(&self, token: Address) -> Result { + // This is a pretty unsophisticated strategy where we basically try a + // bunch of different slots and see which one sticks. We try balance + // mappings for the first `TRIES` slots; each with a unique value. + let mut tries = (0..Self::TRIES).map(|i| { + let strategy = Strategy::Mapping { + slot: U256::from(i), + }; + // Use an exact value which isn't too large or too small. This helps + // not have false positives for cases where the token balances in + // some other denomination from the actual token balance (such as + // stETH for example) and not run into issues with overflows. + let amount = U256::from(u64::from_be_bytes([i; 8])); + + (strategy, amount) + }); + + // On a technical note, Ethereum public addresses are, for the most + // part, generated by taking the 20 last bytes of a Keccak-256 hash (for + // things like contract creation, public address derivation from a + // Secp256k1 public key, etc.), so we use one for our heuristics from a + // 32-byte digest with no know pre-image, to prevent any weird + // interactions with the weird tokens of the world. + let holder = { + let mut address = Address::default(); + address.0.copy_from_slice(&keccak256(b"Moo!")[12..]); + address.0[19] = address.0[19].wrapping_sub(1); + address + }; + + let token = dummy_contract!(ERC20, token); + let call = CallRequest { + to: Some(token.address()), + data: token.methods().balance_of(holder).m.tx.data, + ..Default::default() + }; + let overrides = hashmap! { + token.address() => StateOverride { + state_diff: Some( + tries + .clone() + .map(|(strategy, amount)| strategy.state_override(&holder, &amount)) + .collect(), + ), + ..Default::default() + }, + }; + + let output = self.simulator.simulate(call, overrides, None).await?; + let balance = (output.len() == 32) + .then(|| U256::from_big_endian(&output)) + .ok_or(DetectionError::Decode)?; + + let strategy = tries + .find_map(|(strategy, amount)| (amount == balance).then_some(strategy)) + .ok_or(DetectionError::NotFound)?; + Ok(strategy) + } +} + +/// An error detecting the balance override strategy for a token. +#[derive(Debug, Error)] +pub enum DetectionError { + #[error("could not detect a balance override strategy")] + NotFound, + #[error("unable to decode simulation return data")] + Decode, + #[error(transparent)] + Simulation(#[from] SimulationError), +} From d58ffd7200716c3191930d7debf3bcec089ce693 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Fri, 6 Dec 2024 12:44:36 +0100 Subject: [PATCH 07/25] Enable Auto-detection of Token Balance Overrides (#3149) # Description This PR is a follow up to #3148 and the final PR in the stack. It adds configuration and glue code for enabling the automatic token balance override detection introduced in the aforementioned PR. # Changes - A new `Arguments` type was created for setting balance override configuration and creating the balance overrides instance. - The old implementation of the `BalanceOverriding` trait was superseded by a new one that accepts both a static configuration from the `Arguments` as well as allows optionally for auto-detection - The `Trader` Solidity implementation was changed to only wrap ETH if the trader has sufficient balance. Attempting wrap more than the trader's balance this would cause the simulation to revert without a helpful revert message; and generally means that balance override feature doesn't work for WETH. With the modified implementation, the `Trader.prepareSwap` no longer reverts attempting to wrap more ETH than the balance holds. Now, the `Spardose.requestFunds` will always get called if the `Trader` does not have sufficient `sell_token` balance, and it provides a more helpful message when balance overrides are disabled: ``` 2024-12-04T16:09:19.002Z WARN request{id="2"}:estimator{name="test_quoter"}: shared::price_estimation::trade_verifier: failed verification; returning unverified estimate err=failed to simulate quote Caused by: simulation reverted Some("revert: trader does not have enough sell token") ... ``` ## How to Test An E2E test was modified to include balance override tests for both configured and auto-detected tokens. Additionally, I added a test to ensure that the ETH -> WETH wrapping functionality that previously existed in quote verification simulation did not regress with this change. ## Additional Notes @MartinquaXD commented that the changes to `Trader` did not make much sense. In order to try and address the comment, I moved some code around. In particular, the `Trader` now calls `requestFunds` from the `Spardose` during the `prepareSwap` call (instead of the `Solver` calling `ensureBalance` for the trader when setting up the settlement). IMO this makes a bit more sense conceptually (it is like the trader had a pre-hook that would provide the required funds for the swap). The contract changes are part of this commit: https://github.com/cowprotocol/services/pull/3149/commits/7518ea917a96d98adcf9b3cb7ce618037ae7bc95. ## Configuration The balance overriding feature is **disabled by default**. There are two relevant configuration options that need to be changed in order to enable it: - `QUOTE_TOKEN_BALANCE_OVERRIDES`: a set of `${ADDR}@${SLOT}` configurations for manually enabling token balance overrides for specific tokens (for example, for enabling WETH and USDC on Ethereum Mainnet: `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2@3,0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48@9`). - `QUOTE_AUTODETECT_TOKEN_BALANCE_OVERRIDES`: Set to `true` in order to enable auto-detection of balance override slots. --- crates/contracts/artifacts/Solver.json | 2 +- crates/contracts/artifacts/Spardose.json | 2 +- crates/contracts/artifacts/Swapper.json | 2 +- crates/contracts/artifacts/Trader.json | 2 +- crates/contracts/solidity/Solver.sol | 12 +- crates/contracts/solidity/Spardose.sol | 21 +- crates/contracts/solidity/Trader.sol | 36 ++- .../solidity/libraries/SafeERC20.sol | 13 +- crates/e2e/tests/e2e/quote_verification.rs | 120 +++++++- crates/shared/src/price_estimation/factory.rs | 7 +- crates/shared/src/price_estimation/mod.rs | 22 +- .../src/price_estimation/trade_verifier.rs | 48 ++- .../trade_verifier/balance_overrides.rs | 274 +++++++++++++----- .../balance_overrides/detector.rs | 15 +- 14 files changed, 399 insertions(+), 177 deletions(-) diff --git a/crates/contracts/artifacts/Solver.json b/crates/contracts/artifacts/Solver.json index 0b3ffa8e18..19639cbd96 100644 --- a/crates/contracts/artifacts/Solver.json +++ b/crates/contracts/artifacts/Solver.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"address","name":"spardose","type":"address"}],"internalType":"struct Solver.Mock","name":"mock","type":"tuple"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610b26806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806317b86b161461003b5780633bbb2e1d14610065575b600080fd5b61004e610049366004610851565b61007a565b60405161005c92919061093c565b60405180910390f35b61007861007336600461098a565b6103cf565b005b60006060333014610112576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e000000000000000060648201526084015b60405180910390fd5b8251156102e4576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b15801561019957600080fd5b505af11580156101ad573d6000803e3d6000fd5b50505060208401516040517fc56cca8300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8f811660048301528e81166024830152604482018e9052909116915063c56cca83906064016020604051808303816000875af1158015610234573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061025891906109d5565b6102e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610109565b60008673ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461033f576040519150601f19603f3d011682016040523d82523d6000602084013e610344565b606091505b505090505061035488888f610504565b61035f8d86866105dc565b915061036c88888f610504565b60018054806020026020016040519081016040528092919081815260200182805480156103b857602002820191906000526020600020905b8154815260200190600101908083116103a4575b505050505090509b509b9950505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff86161461049c576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa158015610473573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049791906109f2565b6104b5565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156104fe575a6104dc9082610a3a565b6104e89061116c610a53565b6000808282546104f89190610a53565b90915550505b50505050565b60005b828110156104fe5730633bbb2e1d85858481811061052757610527610a66565b905060200201602081019061053c9190610a95565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156105b157600080fd5b505af11580156105c5573d6000803e3d6000fd5b5050505080806105d490610ab2565b915050610507565b6000805a905061063984848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff89169291505061065b565b506000545a6106489083610a3a565b6106529190610a3a565b95945050505050565b606061066983600084610670565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161069a9190610aea565b60006040518083038185875af1925050503d80600081146106d7576040519150601f19603f3d011682016040523d82523d6000602084013e6106dc565b606091505b5092509050806106ee57815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461071857600080fd5b50565b8035610726816106f6565b919050565b60008083601f84011261073d57600080fd5b50813567ffffffffffffffff81111561075557600080fd5b6020830191508360208260051b850101111561077057600080fd5b9250929050565b60008083601f84011261078957600080fd5b50813567ffffffffffffffff8111156107a157600080fd5b60208301915083602082850101111561077057600080fd5b801515811461071857600080fd5b6000604082840312156107d957600080fd5b6040516040810181811067ffffffffffffffff82111715610823577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040529050808235610834816107b9565b81526020830135610844816106f6565b6020919091015292915050565b60008060008060008060008060008060006101408c8e03121561087357600080fd5b61087d8c356106f6565b8b359a5061088e60208d01356106f6565b60208c013599506108a160408d0161071b565b985060608c013597506108b660808d0161071b565b965067ffffffffffffffff8060a08e013511156108d257600080fd5b6108e28e60a08f01358f0161072b565b90975095506108f360c08e0161071b565b94508060e08e0135111561090657600080fd5b506109178d60e08e01358e01610777565b909350915061092a8d6101008e016107c7565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561097d57845183529383019391830191600101610961565b5090979650505050505050565b60008060006060848603121561099f57600080fd5b83356109aa816106f6565b925060208401356109ba816106f6565b915060408401356109ca816107b9565b809150509250925092565b6000602082840312156109e757600080fd5b8151610669816107b9565b600060208284031215610a0457600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610a4d57610a4d610a0b565b92915050565b80820180821115610a4d57610a4d610a0b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208284031215610aa757600080fd5b8135610669816106f6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610ae357610ae3610a0b565b5060010190565b6000825160005b81811015610b0b5760208186018101518583015201610af1565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317b86b161461003b5780633bbb2e1d14610065575b600080fd5b61004e610049366004610851565b61007a565b60405161005c92919061093c565b60405180910390f35b61007861007336600461098a565b6103cf565b005b60006060333014610112576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e000000000000000060648201526084015b60405180910390fd5b8251156102e4576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b15801561019957600080fd5b505af11580156101ad573d6000803e3d6000fd5b50505060208401516040517fc56cca8300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8f811660048301528e81166024830152604482018e9052909116915063c56cca83906064016020604051808303816000875af1158015610234573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061025891906109d5565b6102e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610109565b60008673ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461033f576040519150601f19603f3d011682016040523d82523d6000602084013e610344565b606091505b505090505061035488888f610504565b61035f8d86866105dc565b915061036c88888f610504565b60018054806020026020016040519081016040528092919081815260200182805480156103b857602002820191906000526020600020905b8154815260200190600101908083116103a4575b505050505090509b509b9950505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff86161461049c576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa158015610473573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049791906109f2565b6104b5565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156104fe575a6104dc9082610a3a565b6104e89061116c610a53565b6000808282546104f89190610a53565b90915550505b50505050565b60005b828110156104fe5730633bbb2e1d85858481811061052757610527610a66565b905060200201602081019061053c9190610a95565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156105b157600080fd5b505af11580156105c5573d6000803e3d6000fd5b5050505080806105d490610ab2565b915050610507565b6000805a905061063984848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff89169291505061065b565b506000545a6106489083610a3a565b6106529190610a3a565b95945050505050565b606061066983600084610670565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161069a9190610aea565b60006040518083038185875af1925050503d80600081146106d7576040519150601f19603f3d011682016040523d82523d6000602084013e6106dc565b606091505b5092509050806106ee57815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461071857600080fd5b50565b8035610726816106f6565b919050565b60008083601f84011261073d57600080fd5b50813567ffffffffffffffff81111561075557600080fd5b6020830191508360208260051b850101111561077057600080fd5b9250929050565b60008083601f84011261078957600080fd5b50813567ffffffffffffffff8111156107a157600080fd5b60208301915083602082850101111561077057600080fd5b801515811461071857600080fd5b6000604082840312156107d957600080fd5b6040516040810181811067ffffffffffffffff82111715610823577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040529050808235610834816107b9565b81526020830135610844816106f6565b6020919091015292915050565b60008060008060008060008060008060006101408c8e03121561087357600080fd5b61087d8c356106f6565b8b359a5061088e60208d01356106f6565b60208c013599506108a160408d0161071b565b985060608c013597506108b660808d0161071b565b965067ffffffffffffffff8060a08e013511156108d257600080fd5b6108e28e60a08f01358f0161072b565b90975095506108f360c08e0161071b565b94508060e08e0135111561090657600080fd5b506109178d60e08e01358e01610777565b909350915061092a8d6101008e016107c7565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561097d57845183529383019391830191600101610961565b5090979650505050505050565b60008060006060848603121561099f57600080fd5b83356109aa816106f6565b925060208401356109ba816106f6565b915060408401356109ca816107b9565b809150509250925092565b6000602082840312156109e757600080fd5b8151610669816107b9565b600060208284031215610a0457600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610a4d57610a4d610a0b565b92915050565b80820180821115610a4d57610a4d610a0b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208284031215610aa757600080fd5b8135610669816106f6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610ae357610ae3610a0b565b5060010190565b6000825160005b81811015610b0b5760208186018101518583015201610af1565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"address","name":"spardose","type":"address"}],"internalType":"struct Solver.Mock","name":"mock","type":"tuple"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506109e0806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806317b86b161461003b5780633bbb2e1d14610065575b600080fd5b61004e61004936600461072c565b61007a565b60405161005c929190610817565b60405180910390f35b610078610073366004610865565b6102aa565b005b60006060333014610111576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b8251156101bf5760208301516040517f66b00f6800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8f811660048301528d81166024830152604482018d90528b811660648301529182166084820152908d16906366b00f689060a401600060405180830381600087803b1580156101a657600080fd5b505af11580156101ba573d6000803e3d6000fd5b505050505b60008673ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461021a576040519150601f19603f3d011682016040523d82523d6000602084013e61021f565b606091505b505090505061022f88888f6103df565b61023a8d86866104b7565b915061024788888f6103df565b600180548060200260200160405190810160405280929190818152602001828054801561029357602002820191906000526020600020905b81548152602001906001019080831161027f575b505050505090509b509b9950505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610377576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561034e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037291906108ac565b610390565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156103d9575a6103b790826108f4565b6103c39061116c61090d565b6000808282546103d3919061090d565b90915550505b50505050565b60005b828110156103d95730633bbb2e1d85858481811061040257610402610920565b9050602002016020810190610417919061094f565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561048c57600080fd5b505af11580156104a0573d6000803e3d6000fd5b5050505080806104af9061096c565b9150506103e2565b6000805a905061051484848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610536565b506000545a61052390836108f4565b61052d91906108f4565b95945050505050565b60606105448360008461054b565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161057591906109a4565b60006040518083038185875af1925050503d80600081146105b2576040519150601f19603f3d011682016040523d82523d6000602084013e6105b7565b606091505b5092509050806105c957815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105f357600080fd5b50565b8035610601816105d1565b919050565b60008083601f84011261061857600080fd5b50813567ffffffffffffffff81111561063057600080fd5b6020830191508360208260051b850101111561064b57600080fd5b9250929050565b60008083601f84011261066457600080fd5b50813567ffffffffffffffff81111561067c57600080fd5b60208301915083602082850101111561064b57600080fd5b8035801515811461060157600080fd5b6000604082840312156106b657600080fd5b6040516040810181811067ffffffffffffffff82111715610700577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405290508061070f83610694565b8152602083013561071f816105d1565b6020919091015292915050565b60008060008060008060008060008060006101408c8e03121561074e57600080fd5b6107588c356105d1565b8b359a5061076960208d01356105d1565b60208c0135995061077c60408d016105f6565b985060608c0135975061079160808d016105f6565b965067ffffffffffffffff8060a08e013511156107ad57600080fd5b6107bd8e60a08f01358f01610606565b90975095506107ce60c08e016105f6565b94508060e08e013511156107e157600080fd5b506107f28d60e08e01358e01610652565b90935091506108058d6101008e016106a4565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156108585784518352938301939183019160010161083c565b5090979650505050505050565b60008060006060848603121561087a57600080fd5b8335610885816105d1565b92506020840135610895816105d1565b91506108a360408501610694565b90509250925092565b6000602082840312156108be57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610907576109076108c5565b92915050565b80820180821115610907576109076108c5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561096157600080fd5b8135610544816105d1565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361099d5761099d6108c5565b5060010190565b6000825160005b818110156109c557602081860181015185830152016109ab565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317b86b161461003b5780633bbb2e1d14610065575b600080fd5b61004e61004936600461072c565b61007a565b60405161005c929190610817565b60405180910390f35b610078610073366004610865565b6102aa565b005b60006060333014610111576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b8251156101bf5760208301516040517f66b00f6800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8f811660048301528d81166024830152604482018d90528b811660648301529182166084820152908d16906366b00f689060a401600060405180830381600087803b1580156101a657600080fd5b505af11580156101ba573d6000803e3d6000fd5b505050505b60008673ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461021a576040519150601f19603f3d011682016040523d82523d6000602084013e61021f565b606091505b505090505061022f88888f6103df565b61023a8d86866104b7565b915061024788888f6103df565b600180548060200260200160405190810160405280929190818152602001828054801561029357602002820191906000526020600020905b81548152602001906001019080831161027f575b505050505090509b509b9950505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610377576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561034e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037291906108ac565b610390565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156103d9575a6103b790826108f4565b6103c39061116c61090d565b6000808282546103d3919061090d565b90915550505b50505050565b60005b828110156103d95730633bbb2e1d85858481811061040257610402610920565b9050602002016020810190610417919061094f565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561048c57600080fd5b505af11580156104a0573d6000803e3d6000fd5b5050505080806104af9061096c565b9150506103e2565b6000805a905061051484848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610536565b506000545a61052390836108f4565b61052d91906108f4565b95945050505050565b60606105448360008461054b565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161057591906109a4565b60006040518083038185875af1925050503d80600081146105b2576040519150601f19603f3d011682016040523d82523d6000602084013e6105b7565b606091505b5092509050806105c957815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105f357600080fd5b50565b8035610601816105d1565b919050565b60008083601f84011261061857600080fd5b50813567ffffffffffffffff81111561063057600080fd5b6020830191508360208260051b850101111561064b57600080fd5b9250929050565b60008083601f84011261066457600080fd5b50813567ffffffffffffffff81111561067c57600080fd5b60208301915083602082850101111561064b57600080fd5b8035801515811461060157600080fd5b6000604082840312156106b657600080fd5b6040516040810181811067ffffffffffffffff82111715610700577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405290508061070f83610694565b8152602083013561071f816105d1565b6020919091015292915050565b60008060008060008060008060008060006101408c8e03121561074e57600080fd5b6107588c356105d1565b8b359a5061076960208d01356105d1565b60208c0135995061077c60408d016105f6565b985060608c0135975061079160808d016105f6565b965067ffffffffffffffff8060a08e013511156107ad57600080fd5b6107bd8e60a08f01358f01610606565b90975095506107ce60c08e016105f6565b94508060e08e013511156107e157600080fd5b506107f28d60e08e01358e01610652565b90935091506108058d6101008e016106a4565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156108585784518352938301939183019160010161083c565b5090979650505050505050565b60008060006060848603121561087a57600080fd5b8335610885816105d1565b92506020840135610895816105d1565b91506108a360408501610694565b90509250925092565b6000602082840312156108be57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610907576109076108c5565b92915050565b80820180821115610907576109076108c5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561096157600080fd5b8135610544816105d1565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361099d5761099d6108c5565b5060010190565b6000825160005b818110156109c557602081860181015185830152016109ab565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Spardose.json b/crates/contracts/artifacts/Spardose.json index 08787fb5a7..a801fc11d3 100644 --- a/crates/contracts/artifacts/Spardose.json +++ b/crates/contracts/artifacts/Spardose.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"address","name":"trader","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ensureBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610364806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c56cca8314610030575b600080fd5b61004361003e366004610277565b610057565b604051901515815260200160405180910390f35b6040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015260009182918516906370a0823190602401602060405180830381865afa1580156100c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ec91906102b3565b90508281106100ff576001915050610133565b600061010b82856102cc565b905061012e73ffffffffffffffffffffffffffffffffffffffff8616878361013a565b925050505b9392505050565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790529151600092606091908716906101d3908490610306565b6000604051808303816000865af19150503d8060008114610210576040519150601f19603f3d011682016040523d82523d6000602084013e610215565b606091505b50909350905082801561012e575061012e8160008151600014806102485750818060200190518101906102489190610335565b92915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461027257600080fd5b919050565b60008060006060848603121561028c57600080fd5b6102958461024e565b92506102a36020850161024e565b9150604084013590509250925092565b6000602082840312156102c557600080fd5b5051919050565b81810381811115610248577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825160005b81811015610327576020818601810151858301520161030d565b506000920191825250919050565b60006020828403121561034757600080fd5b8151801515811461013357600080fdfea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c56cca8314610030575b600080fd5b61004361003e366004610277565b610057565b604051901515815260200160405180910390f35b6040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff848116600483015260009182918516906370a0823190602401602060405180830381865afa1580156100c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ec91906102b3565b90508281106100ff576001915050610133565b600061010b82856102cc565b905061012e73ffffffffffffffffffffffffffffffffffffffff8616878361013a565b925050505b9392505050565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790529151600092606091908716906101d3908490610306565b6000604051808303816000865af19150503d8060008114610210576040519150601f19603f3d011682016040523d82523d6000602084013e610215565b606091505b50909350905082801561012e575061012e8160008151600014806102485750818060200190518101906102489190610335565b92915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461027257600080fd5b919050565b60008060006060848603121561028c57600080fd5b6102958461024e565b92506102a36020850161024e565b9150604084013590509250925092565b6000602082840312156102c557600080fd5b5051919050565b81810381811115610248577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825160005b81811015610327576020818601810151858301520161030d565b506000920191825250919050565b60006020828403121561034757600080fd5b8151801515811461013357600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"requestFunds","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506102de806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063494666b614610030575b600080fd5b61004361003e36600461023b565b610045565b005b61006673ffffffffffffffffffffffffffffffffffffffff8316338361006a565b5050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052906000906100fd90861683610179565b90506101088161018e565b610172576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a207472616e73666572206661696c6564000000000000604482015260640160405180910390fd5b5050505050565b6060610187836000846101b5565b9392505050565b60008151600014806101af5750818060200190518101906101af9190610280565b92915050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516101df91906102a2565b60006040518083038185875af1925050503d806000811461021c576040519150601f19603f3d011682016040523d82523d6000602084013e610221565b606091505b50925090508061023357815160208301fd5b509392505050565b6000806040838503121561024e57600080fd5b823573ffffffffffffffffffffffffffffffffffffffff8116811461027257600080fd5b946020939093013593505050565b60006020828403121561029257600080fd5b8151801515811461018757600080fd5b6000825160005b818110156102c357602081860181015185830152016102a9565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063494666b614610030575b600080fd5b61004361003e36600461023b565b610045565b005b61006673ffffffffffffffffffffffffffffffffffffffff8316338361006a565b5050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052906000906100fd90861683610179565b90506101088161018e565b610172576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a207472616e73666572206661696c6564000000000000604482015260640160405180910390fd5b5050505050565b6060610187836000846101b5565b9392505050565b60008151600014806101af5750818060200190518101906101af9190610280565b92915050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516101df91906102a2565b60006040518083038185875af1925050503d806000811461021c576040519150601f19603f3d011682016040523d82523d6000602084013e610221565b606091505b50925090508061023357815160208301fd5b509392505050565b6000806040838503121561024e57600080fd5b823573ffffffffffffffffffffffffffffffffffffffff8116811461027257600080fd5b946020939093013593505050565b60006020828403121561029257600080fd5b8151801515811461018757600080fd5b6000825160005b818110156102c357602081860181015185830152016102a9565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Swapper.json b/crates/contracts/artifacts/Swapper.json index 7f6566ee0d..6efb4a8590 100644 --- a/crates/contracts/artifacts/Swapper.json +++ b/crates/contracts/artifacts/Swapper.json @@ -1 +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":"0x608060405234801561001057600080fd5b506111d5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80631626ba7e1461003b5780639ed280dd146100a7575b600080fd5b610071610049366004610b16565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b6100ba6100b5366004610bcf565b6100c8565b60405190815260200161009e565b6000602085018035906100db9087610c5c565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610147573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016b9190610c79565b1015610179575060006108b8565b6102178673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101eb9190610c92565b60006101fa6020890189610c5c565b73ffffffffffffffffffffffffffffffffffffffff1691906108c1565b61029a8673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610265573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102899190610c92565b602087018035906101fa9089610c5c565b6040805160028082526060820183526000926020830190803683370190505090506102c86020870187610c5c565b816000815181106102db576102db610cde565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910182015261030b90860186610c5c565b8160018151811061031e5761031e610cde565b73ffffffffffffffffffffffffffffffffffffffff92909216602092830291909101820152604080516002808252606082018352600093919290918301908036833701905050905085602001358160008151811061037e5761037e610cde565b6020026020010181815250508660200135816001815181106103a2576103a2610cde565b6020908102919091010152604080516001808252818301909252600091816020015b6104406040518061016001604052806000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600063ffffffff16815260200160008019168152602001600081526020016000815260200160008152602001606081525090565b8152602001906001900390816103c45790505090506040518061016001604052806000815260200160018152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001896020013581526020018860200135815260200163ffffffff801681526020016000801b815260200160008152602001604081526020016000815260200130604051602001610506919060609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815260140190565b6040516020818303038152906040528152508160008151811061052b5761052b610cde565b602002602001018190525061053e610aef565b60208088013590610551908b018b610c5c565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e8c61057a60208c018c610c5c565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061060e9190610c79565b101561078c5760408051600180825281830190925290816020015b60408051606080820183526000808352602083015291810191909152815260200190600190039081610629575050815261066660208a018a610c5c565b8151805160009061067957610679610cde565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff9092169091526106ac908a018a610c5c565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b36106d460208a018a610c5c565b60405173ffffffffffffffffffffffffffffffffffffffff909116602482015260208a01356044820152606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b929092179091529050816000602002015160008151811061077c5761077c610cde565b6020026020010151604001819052505b60408051600180825281830190925290816020015b604080516060808201835260008083526020830152918101919091528152602001906001900390816107a157505060208201526107dd86610d85565b602082015180516000906107f3576107f3610cde565b60200260200101819052506108b18a73ffffffffffffffffffffffffffffffffffffffff166313d79a0b868686866040516024016108349493929190611084565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b9290921790915273ffffffffffffffffffffffffffffffffffffffff8d16915061099c565b9450505050505b95945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b30000000000000000000000000000000000000000000000000000000017905290600090610954908616836109b1565b9050610995816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506109bf565b5050505050565b60006109aa83600084610a0f565b9392505050565b60606109aa83600084610a42565b6109c882610ac8565b8190610a0a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a01919061113d565b60405180910390fd5b505050565b60005a905060008083516020850186885af1610a2f573d6000803e3d6000fd5b5a610a3a9082611150565b949350505050565b606060008473ffffffffffffffffffffffffffffffffffffffff168484604051610a6c919061118a565b60006040518083038185875af1925050503d8060008114610aa9576040519150601f19603f3d011682016040523d82523d6000602084013e610aae565b606091505b509250905080610ac057815160208301fd5b509392505050565b6000815160001480610ae9575081806020019051810190610ae991906111a6565b92915050565b60405180606001604052806003905b6060815260200190600190039081610afe5790505090565b600080600060408486031215610b2b57600080fd5b83359250602084013567ffffffffffffffff80821115610b4a57600080fd5b818601915086601f830112610b5e57600080fd5b813581811115610b6d57600080fd5b876020828501011115610b7f57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610bb457600080fd5b50565b600060408284031215610bc957600080fd5b50919050565b60008060008060006101008688031215610be857600080fd5b8535610bf381610b92565b9450610c028760208801610bb7565b9350610c118760608801610bb7565b9250610c208760a08801610bb7565b915060e086013567ffffffffffffffff811115610c3c57600080fd5b860160608189031215610c4e57600080fd5b809150509295509295909350565b600060208284031215610c6e57600080fd5b81356109aa81610b92565b600060208284031215610c8b57600080fd5b5051919050565b600060208284031215610ca457600080fd5b81516109aa81610b92565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6040516060810167ffffffffffffffff81118282101715610d3057610d30610caf565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d7d57610d7d610caf565b604052919050565b600060608236031215610d9757600080fd5b610d9f610d0d565b8235610daa81610b92565b815260208381013581830152604084013567ffffffffffffffff80821115610dd157600080fd5b9085019036601f830112610de457600080fd5b813581811115610df657610df6610caf565b610e26847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610d36565b91508082523684828501011115610e3c57600080fd5b808484018584013760009082019093019290925250604082015292915050565b60005b83811015610e77578181015183820152602001610e5f565b50506000910152565b60008151808452610e98816020860160208601610e5c565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015610faa5782840389528151610160815186528682015187870152604080830151610f2d8289018273ffffffffffffffffffffffffffffffffffffffff169052565b5050606082810151908701526080808301519087015260a08083015163ffffffff169087015260c0808301519087015260e080830151908701526101008083015190870152610120808301519087015261014091820151918601819052610f9681870183610e80565b9a87019a9550505090840190600101610ee8565b5091979650505050505050565b6000826060808201846000805b6003811015610faa578584038952825180518086526020918201918087019190600582901b88018101865b8381101561106d578982037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00185528551805173ffffffffffffffffffffffffffffffffffffffff16835283810151848401526040908101519083018c905261105a8c840182610e80565b9684019695840195925050600101610fef565b509c81019c97509590950194505050600101610fc4565b6080808252855190820181905260009060209060a0840190828901845b828110156110d357815173ffffffffffffffffffffffffffffffffffffffff16845292840192908401906001016110a1565b5050508381038285015286518082528783019183019060005b81811015611108578351835292840192918401916001016110ec565b5050848103604086015261111c8188610eca565b9250505082810360608401526111328185610fb7565b979650505050505050565b6020815260006109aa6020830184610e80565b81810381811115610ae9577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825161119c818460208701610e5c565b9190910192915050565b6000602082840312156111b857600080fd5b815180151581146109aa57600080fdfea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80631626ba7e1461003b5780639ed280dd146100a7575b600080fd5b610071610049366004610b16565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b6100ba6100b5366004610bcf565b6100c8565b60405190815260200161009e565b6000602085018035906100db9087610c5c565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610147573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016b9190610c79565b1015610179575060006108b8565b6102178673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101eb9190610c92565b60006101fa6020890189610c5c565b73ffffffffffffffffffffffffffffffffffffffff1691906108c1565b61029a8673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610265573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102899190610c92565b602087018035906101fa9089610c5c565b6040805160028082526060820183526000926020830190803683370190505090506102c86020870187610c5c565b816000815181106102db576102db610cde565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910182015261030b90860186610c5c565b8160018151811061031e5761031e610cde565b73ffffffffffffffffffffffffffffffffffffffff92909216602092830291909101820152604080516002808252606082018352600093919290918301908036833701905050905085602001358160008151811061037e5761037e610cde565b6020026020010181815250508660200135816001815181106103a2576103a2610cde565b6020908102919091010152604080516001808252818301909252600091816020015b6104406040518061016001604052806000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600063ffffffff16815260200160008019168152602001600081526020016000815260200160008152602001606081525090565b8152602001906001900390816103c45790505090506040518061016001604052806000815260200160018152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001896020013581526020018860200135815260200163ffffffff801681526020016000801b815260200160008152602001604081526020016000815260200130604051602001610506919060609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815260140190565b6040516020818303038152906040528152508160008151811061052b5761052b610cde565b602002602001018190525061053e610aef565b60208088013590610551908b018b610c5c565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e8c61057a60208c018c610c5c565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061060e9190610c79565b101561078c5760408051600180825281830190925290816020015b60408051606080820183526000808352602083015291810191909152815260200190600190039081610629575050815261066660208a018a610c5c565b8151805160009061067957610679610cde565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff9092169091526106ac908a018a610c5c565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b36106d460208a018a610c5c565b60405173ffffffffffffffffffffffffffffffffffffffff909116602482015260208a01356044820152606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b929092179091529050816000602002015160008151811061077c5761077c610cde565b6020026020010151604001819052505b60408051600180825281830190925290816020015b604080516060808201835260008083526020830152918101919091528152602001906001900390816107a157505060208201526107dd86610d85565b602082015180516000906107f3576107f3610cde565b60200260200101819052506108b18a73ffffffffffffffffffffffffffffffffffffffff166313d79a0b868686866040516024016108349493929190611084565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b9290921790915273ffffffffffffffffffffffffffffffffffffffff8d16915061099c565b9450505050505b95945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b30000000000000000000000000000000000000000000000000000000017905290600090610954908616836109b1565b9050610995816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506109bf565b5050505050565b60006109aa83600084610a0f565b9392505050565b60606109aa83600084610a42565b6109c882610ac8565b8190610a0a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a01919061113d565b60405180910390fd5b505050565b60005a905060008083516020850186885af1610a2f573d6000803e3d6000fd5b5a610a3a9082611150565b949350505050565b606060008473ffffffffffffffffffffffffffffffffffffffff168484604051610a6c919061118a565b60006040518083038185875af1925050503d8060008114610aa9576040519150601f19603f3d011682016040523d82523d6000602084013e610aae565b606091505b509250905080610ac057815160208301fd5b509392505050565b6000815160001480610ae9575081806020019051810190610ae991906111a6565b92915050565b60405180606001604052806003905b6060815260200190600190039081610afe5790505090565b600080600060408486031215610b2b57600080fd5b83359250602084013567ffffffffffffffff80821115610b4a57600080fd5b818601915086601f830112610b5e57600080fd5b813581811115610b6d57600080fd5b876020828501011115610b7f57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610bb457600080fd5b50565b600060408284031215610bc957600080fd5b50919050565b60008060008060006101008688031215610be857600080fd5b8535610bf381610b92565b9450610c028760208801610bb7565b9350610c118760608801610bb7565b9250610c208760a08801610bb7565b915060e086013567ffffffffffffffff811115610c3c57600080fd5b860160608189031215610c4e57600080fd5b809150509295509295909350565b600060208284031215610c6e57600080fd5b81356109aa81610b92565b600060208284031215610c8b57600080fd5b5051919050565b600060208284031215610ca457600080fd5b81516109aa81610b92565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6040516060810167ffffffffffffffff81118282101715610d3057610d30610caf565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d7d57610d7d610caf565b604052919050565b600060608236031215610d9757600080fd5b610d9f610d0d565b8235610daa81610b92565b815260208381013581830152604084013567ffffffffffffffff80821115610dd157600080fd5b9085019036601f830112610de457600080fd5b813581811115610df657610df6610caf565b610e26847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610d36565b91508082523684828501011115610e3c57600080fd5b808484018584013760009082019093019290925250604082015292915050565b60005b83811015610e77578181015183820152602001610e5f565b50506000910152565b60008151808452610e98816020860160208601610e5c565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015610faa5782840389528151610160815186528682015187870152604080830151610f2d8289018273ffffffffffffffffffffffffffffffffffffffff169052565b5050606082810151908701526080808301519087015260a08083015163ffffffff169087015260c0808301519087015260e080830151908701526101008083015190870152610120808301519087015261014091820151918601819052610f9681870183610e80565b9a87019a9550505090840190600101610ee8565b5091979650505050505050565b6000826060808201846000805b6003811015610faa578584038952825180518086526020918201918087019190600582901b88018101865b8381101561106d578982037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00185528551805173ffffffffffffffffffffffffffffffffffffffff16835283810151848401526040908101519083018c905261105a8c840182610e80565b9684019695840195925050600101610fef565b509c81019c97509590950194505050600101610fc4565b6080808252855190820181905260009060209060a0840190828901845b828110156110d357815173ffffffffffffffffffffffffffffffffffffffff16845292840192908401906001016110a1565b5050508381038285015286518082528783019183019060005b81811015611108578351835292840192918401916001016110ec565b5050848103604086015261111c8188610eca565b9250505082810360608401526111328185610fb7565b979650505050505050565b6020815260006109aa6020830184610e80565b81810381811115610ae9577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825161119c818460208701610e5c565b9190910192915050565b6000602082840312156111b857600080fd5b815180151581146109aa57600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"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":"0x608060405234801561001057600080fd5b506111a6806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80631626ba7e1461003b5780639ed280dd146100a7575b600080fd5b610071610049366004610afa565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b6100ba6100b5366004610bb3565b6100c8565b60405190815260200161009e565b6000602085018035906100db9087610c40565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610147573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016b9190610c5d565b1015610179575060006108b8565b6102178673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101eb9190610c76565b60006101fa6020890189610c40565b73ffffffffffffffffffffffffffffffffffffffff1691906108c1565b61029a8673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610265573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102899190610c76565b602087018035906101fa9089610c40565b6040805160028082526060820183526000926020830190803683370190505090506102c86020870187610c40565b816000815181106102db576102db610cc2565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910182015261030b90860186610c40565b8160018151811061031e5761031e610cc2565b73ffffffffffffffffffffffffffffffffffffffff92909216602092830291909101820152604080516002808252606082018352600093919290918301908036833701905050905085602001358160008151811061037e5761037e610cc2565b6020026020010181815250508660200135816001815181106103a2576103a2610cc2565b6020908102919091010152604080516001808252818301909252600091816020015b6104406040518061016001604052806000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600063ffffffff16815260200160008019168152602001600081526020016000815260200160008152602001606081525090565b8152602001906001900390816103c45790505090506040518061016001604052806000815260200160018152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001896020013581526020018860200135815260200163ffffffff801681526020016000801b815260200160008152602001604081526020016000815260200130604051602001610506919060609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815260140190565b6040516020818303038152906040528152508160008151811061052b5761052b610cc2565b602002602001018190525061053e610ad3565b60208088013590610551908b018b610c40565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e8c61057a60208c018c610c40565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061060e9190610c5d565b101561078c5760408051600180825281830190925290816020015b60408051606080820183526000808352602083015291810191909152815260200190600190039081610629575050815261066660208a018a610c40565b8151805160009061067957610679610cc2565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff9092169091526106ac908a018a610c40565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b36106d460208a018a610c40565b60405173ffffffffffffffffffffffffffffffffffffffff909116602482015260208a01356044820152606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b929092179091529050816000602002015160008151811061077c5761077c610cc2565b6020026020010151604001819052505b60408051600180825281830190925290816020015b604080516060808201835260008083526020830152918101919091528152602001906001900390816107a157505060208201526107dd86610d69565b602082015180516000906107f3576107f3610cc2565b60200260200101819052506108b18a73ffffffffffffffffffffffffffffffffffffffff166313d79a0b868686866040516024016108349493929190611068565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b9290921790915273ffffffffffffffffffffffffffffffffffffffff8d1691506109d0565b9450505050505b95945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b30000000000000000000000000000000000000000000000000000000017905290600090610954908616836109e5565b905061095f816109f3565b6109c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640160405180910390fd5b5050505050565b60006109de83600084610a1a565b9392505050565b60606109de83600084610a4d565b6000815160001480610a14575081806020019051810190610a149190611121565b92915050565b60005a905060008083516020850186885af1610a3a573d6000803e3d6000fd5b5a610a459082611143565b949350505050565b606060008473ffffffffffffffffffffffffffffffffffffffff168484604051610a77919061117d565b60006040518083038185875af1925050503d8060008114610ab4576040519150601f19603f3d011682016040523d82523d6000602084013e610ab9565b606091505b509250905080610acb57815160208301fd5b509392505050565b60405180606001604052806003905b6060815260200190600190039081610ae25790505090565b600080600060408486031215610b0f57600080fd5b83359250602084013567ffffffffffffffff80821115610b2e57600080fd5b818601915086601f830112610b4257600080fd5b813581811115610b5157600080fd5b876020828501011115610b6357600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610b9857600080fd5b50565b600060408284031215610bad57600080fd5b50919050565b60008060008060006101008688031215610bcc57600080fd5b8535610bd781610b76565b9450610be68760208801610b9b565b9350610bf58760608801610b9b565b9250610c048760a08801610b9b565b915060e086013567ffffffffffffffff811115610c2057600080fd5b860160608189031215610c3257600080fd5b809150509295509295909350565b600060208284031215610c5257600080fd5b81356109de81610b76565b600060208284031215610c6f57600080fd5b5051919050565b600060208284031215610c8857600080fd5b81516109de81610b76565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6040516060810167ffffffffffffffff81118282101715610d1457610d14610c93565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d6157610d61610c93565b604052919050565b600060608236031215610d7b57600080fd5b610d83610cf1565b8235610d8e81610b76565b815260208381013581830152604084013567ffffffffffffffff80821115610db557600080fd5b9085019036601f830112610dc857600080fd5b813581811115610dda57610dda610c93565b610e0a847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610d1a565b91508082523684828501011115610e2057600080fd5b808484018584013760009082019093019290925250604082015292915050565b60005b83811015610e5b578181015183820152602001610e43565b50506000910152565b60008151808452610e7c816020860160208601610e40565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015610f8e5782840389528151610160815186528682015187870152604080830151610f118289018273ffffffffffffffffffffffffffffffffffffffff169052565b5050606082810151908701526080808301519087015260a08083015163ffffffff169087015260c0808301519087015260e080830151908701526101008083015190870152610120808301519087015261014091820151918601819052610f7a81870183610e64565b9a87019a9550505090840190600101610ecc565b5091979650505050505050565b6000826060808201846000805b6003811015610f8e578584038952825180518086526020918201918087019190600582901b88018101865b83811015611051578982037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00185528551805173ffffffffffffffffffffffffffffffffffffffff16835283810151848401526040908101519083018c905261103e8c840182610e64565b9684019695840195925050600101610fd3565b509c81019c97509590950194505050600101610fa8565b6080808252855190820181905260009060209060a0840190828901845b828110156110b757815173ffffffffffffffffffffffffffffffffffffffff1684529284019290840190600101611085565b5050508381038285015286518082528783019183019060005b818110156110ec578351835292840192918401916001016110d0565b505084810360408601526111008188610eae565b9250505082810360608401526111168185610f9b565b979650505050505050565b60006020828403121561113357600080fd5b815180151581146109de57600080fd5b81810381811115610a14577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825161118f818460208701610e40565b919091019291505056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80631626ba7e1461003b5780639ed280dd146100a7575b600080fd5b610071610049366004610afa565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020015b60405180910390f35b6100ba6100b5366004610bb3565b6100c8565b60405190815260200161009e565b6000602085018035906100db9087610c40565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff91909116906370a0823190602401602060405180830381865afa158015610147573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016b9190610c5d565b1015610179575060006108b8565b6102178673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101eb9190610c76565b60006101fa6020890189610c40565b73ffffffffffffffffffffffffffffffffffffffff1691906108c1565b61029a8673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610265573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102899190610c76565b602087018035906101fa9089610c40565b6040805160028082526060820183526000926020830190803683370190505090506102c86020870187610c40565b816000815181106102db576102db610cc2565b73ffffffffffffffffffffffffffffffffffffffff90921660209283029190910182015261030b90860186610c40565b8160018151811061031e5761031e610cc2565b73ffffffffffffffffffffffffffffffffffffffff92909216602092830291909101820152604080516002808252606082018352600093919290918301908036833701905050905085602001358160008151811061037e5761037e610cc2565b6020026020010181815250508660200135816001815181106103a2576103a2610cc2565b6020908102919091010152604080516001808252818301909252600091816020015b6104406040518061016001604052806000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600063ffffffff16815260200160008019168152602001600081526020016000815260200160008152602001606081525090565b8152602001906001900390816103c45790505090506040518061016001604052806000815260200160018152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001896020013581526020018860200135815260200163ffffffff801681526020016000801b815260200160008152602001604081526020016000815260200130604051602001610506919060609190911b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016815260140190565b6040516020818303038152906040528152508160008151811061052b5761052b610cc2565b602002602001018190525061053e610ad3565b60208088013590610551908b018b610c40565b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e8c61057a60208c018c610c40565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061060e9190610c5d565b101561078c5760408051600180825281830190925290816020015b60408051606080820183526000808352602083015291810191909152815260200190600190039081610629575050815261066660208a018a610c40565b8151805160009061067957610679610cc2565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff9092169091526106ac908a018a610c40565b73ffffffffffffffffffffffffffffffffffffffff1663095ea7b36106d460208a018a610c40565b60405173ffffffffffffffffffffffffffffffffffffffff909116602482015260208a01356044820152606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b929092179091529050816000602002015160008151811061077c5761077c610cc2565b6020026020010151604001819052505b60408051600180825281830190925290816020015b604080516060808201835260008083526020830152918101919091528152602001906001900390816107a157505060208201526107dd86610d69565b602082015180516000906107f3576107f3610cc2565b60200260200101819052506108b18a73ffffffffffffffffffffffffffffffffffffffff166313d79a0b868686866040516024016108349493929190611068565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09390931b9290921790915273ffffffffffffffffffffffffffffffffffffffff8d1691506109d0565b9450505050505b95945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b30000000000000000000000000000000000000000000000000000000017905290600090610954908616836109e5565b905061095f816109f3565b6109c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640160405180910390fd5b5050505050565b60006109de83600084610a1a565b9392505050565b60606109de83600084610a4d565b6000815160001480610a14575081806020019051810190610a149190611121565b92915050565b60005a905060008083516020850186885af1610a3a573d6000803e3d6000fd5b5a610a459082611143565b949350505050565b606060008473ffffffffffffffffffffffffffffffffffffffff168484604051610a77919061117d565b60006040518083038185875af1925050503d8060008114610ab4576040519150601f19603f3d011682016040523d82523d6000602084013e610ab9565b606091505b509250905080610acb57815160208301fd5b509392505050565b60405180606001604052806003905b6060815260200190600190039081610ae25790505090565b600080600060408486031215610b0f57600080fd5b83359250602084013567ffffffffffffffff80821115610b2e57600080fd5b818601915086601f830112610b4257600080fd5b813581811115610b5157600080fd5b876020828501011115610b6357600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610b9857600080fd5b50565b600060408284031215610bad57600080fd5b50919050565b60008060008060006101008688031215610bcc57600080fd5b8535610bd781610b76565b9450610be68760208801610b9b565b9350610bf58760608801610b9b565b9250610c048760a08801610b9b565b915060e086013567ffffffffffffffff811115610c2057600080fd5b860160608189031215610c3257600080fd5b809150509295509295909350565b600060208284031215610c5257600080fd5b81356109de81610b76565b600060208284031215610c6f57600080fd5b5051919050565b600060208284031215610c8857600080fd5b81516109de81610b76565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6040516060810167ffffffffffffffff81118282101715610d1457610d14610c93565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610d6157610d61610c93565b604052919050565b600060608236031215610d7b57600080fd5b610d83610cf1565b8235610d8e81610b76565b815260208381013581830152604084013567ffffffffffffffff80821115610db557600080fd5b9085019036601f830112610dc857600080fd5b813581811115610dda57610dda610c93565b610e0a847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610d1a565b91508082523684828501011115610e2057600080fd5b808484018584013760009082019093019290925250604082015292915050565b60005b83811015610e5b578181015183820152602001610e43565b50506000910152565b60008151808452610e7c816020860160208601610e40565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015610f8e5782840389528151610160815186528682015187870152604080830151610f118289018273ffffffffffffffffffffffffffffffffffffffff169052565b5050606082810151908701526080808301519087015260a08083015163ffffffff169087015260c0808301519087015260e080830151908701526101008083015190870152610120808301519087015261014091820151918601819052610f7a81870183610e64565b9a87019a9550505090840190600101610ecc565b5091979650505050505050565b6000826060808201846000805b6003811015610f8e578584038952825180518086526020918201918087019190600582901b88018101865b83811015611051578982037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00185528551805173ffffffffffffffffffffffffffffffffffffffff16835283810151848401526040908101519083018c905261103e8c840182610e64565b9684019695840195925050600101610fd3565b509c81019c97509590950194505050600101610fa8565b6080808252855190820181905260009060209060a0840190828901845b828110156110b757815173ffffffffffffffffffffffffffffffffffffffff1684529284019290840190600101611085565b5050508381038285015286518082528783019183019060005b818110156110ec578351835292840192918401916001016110d0565b505084810360408601526111008188610eae565b9250505082810360608401526111168185610f9b565b979650505050505050565b60006020828403121561113357600080fd5b815180151581146109de57600080fd5b81810381811115610a14577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825161118f818460208701610e40565b919091019291505056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Trader.json b/crates/contracts/artifacts/Trader.json index 2614e657ac..eef29787b2 100644 --- a/crates/contracts/artifacts/Trader.json +++ b/crates/contracts/artifacts/Trader.json @@ -1 +1 @@ -{"abi":[{"stateMutability":"payable","type":"fallback"},{"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":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610a97806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610873565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610914565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff1683604051610149919061098b565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61065b565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061030091906109a7565b9050828110156103e557600061031682856109c0565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061047591906109fa565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061050991906109a7565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058591906109fa565b73ffffffffffffffffffffffffffffffffffffffff8616906000610696565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061691906109fa565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610696565b5050505050565b60008061068960017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c97356109c0565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906107299086168361076a565b9050610654816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c656400000000000081525061077f565b6060610778836000846107c6565b9392505050565b6107888261084c565b81906107c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610a17565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516107f0919061098b565b60006040518083038185875af1925050503d806000811461082d576040519150601f19603f3d011682016040523d82523d6000602084013e610832565b606091505b50925090508061084457815160208301fd5b509392505050565b600081516000148061086d57508180602001905181019061086d9190610a68565b92915050565b60008060006040848603121561088857600080fd5b83359250602084013567ffffffffffffffff808211156108a757600080fd5b818601915086601f8301126108bb57600080fd5b8135818111156108ca57600080fd5b8760208285010111156108dc57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff8116811461091157600080fd5b50565b6000806000806080858703121561092a57600080fd5b8435610935816108ef565b93506020850135610945816108ef565b925060408501359150606085013561095c816108ef565b939692955090935050565b60005b8381101561098257818101518382015260200161096a565b50506000910152565b6000825161099d818460208701610967565b9190910192915050565b6000602082840312156109b957600080fd5b5051919050565b8181038181111561086d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610a0c57600080fd5b8151610778816108ef565b6020815260008251806020840152610a36816040850160208701610967565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b600060208284031215610a7a57600080fd5b8151801515811461077857600080fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610873565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610914565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff1683604051610149919061098b565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61065b565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061030091906109a7565b9050828110156103e557600061031682856109c0565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061047591906109fa565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061050991906109a7565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058591906109fa565b73ffffffffffffffffffffffffffffffffffffffff8616906000610696565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061691906109fa565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610696565b5050505050565b60008061068960017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c97356109c0565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906107299086168361076a565b9050610654816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c656400000000000081525061077f565b6060610778836000846107c6565b9392505050565b6107888261084c565b81906107c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610a17565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516107f0919061098b565b60006040518083038185875af1925050503d806000811461082d576040519150601f19603f3d011682016040523d82523d6000602084013e610832565b606091505b50925090508061084457815160208301fd5b509392505050565b600081516000148061086d57508180602001905181019061086d9190610a68565b92915050565b60008060006040848603121561088857600080fd5b83359250602084013567ffffffffffffffff808211156108a757600080fd5b818601915086601f8301126108bb57600080fd5b8135818111156108ca57600080fd5b8760208285010111156108dc57600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff8116811461091157600080fd5b50565b6000806000806080858703121561092a57600080fd5b8435610935816108ef565b93506020850135610945816108ef565b925060408501359150606085013561095c816108ef565b939692955090935050565b60005b8381101561098257818101518382015260200161096a565b50506000910152565b6000825161099d818460208701610967565b9190910192915050565b6000602082840312156109b957600080fd5b5051919050565b8181038181111561086d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610a0c57600080fd5b8151610778816108ef565b6020815260008251806020840152610a36816040850160208701610967565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b600060208284031215610a7a57600080fd5b8151801515811461077857600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"stateMutability":"payable","type":"fallback"},{"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":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address","name":"spardose","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610ba4806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e1461008657806366b00f68146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a13660046109cd565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a6e565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610ad6565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa6107c5565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610b05565b9050838110156103835760006103168286610b1e565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008473ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308873ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104139190610b58565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a79190610b05565b9050838110156105f2576105428673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104ff573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105239190610b58565b73ffffffffffffffffffffffffffffffffffffffff8716906000610800565b6105f28673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610590573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b49190610b58565b73ffffffffffffffffffffffffffffffffffffffff8716907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610800565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa15801561065f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106839190610b05565b9050848110156107bc5773ffffffffffffffffffffffffffffffffffffffff831663494666b6876106b48489610b1e565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b15801561071f57600080fd5b505af1925050508015610730575060015b6107bc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b50505050505050565b6000806107f360017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610b1e565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906108939086168361090b565b905061089e81610920565b610904576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c65640000000000006044820152606401610233565b5050505050565b606061091983600084610947565b9392505050565b60008151600014806109415750818060200190518101906109419190610b75565b92915050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516109719190610ad6565b60006040518083038185875af1925050503d80600081146109ae576040519150601f19603f3d011682016040523d82523d6000602084013e6109b3565b606091505b5092509050806109c557815160208301fd5b509392505050565b6000806000604084860312156109e257600080fd5b83359250602084013567ffffffffffffffff80821115610a0157600080fd5b818601915086601f830112610a1557600080fd5b813581811115610a2457600080fd5b876020828501011115610a3657600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a6b57600080fd5b50565b600080600080600060a08688031215610a8657600080fd5b8535610a9181610a49565b94506020860135610aa181610a49565b9350604086013592506060860135610ab881610a49565b91506080860135610ac881610a49565b809150509295509295909350565b6000825160005b81811015610af75760208186018101518583015201610add565b506000920191825250919050565b600060208284031215610b1757600080fd5b5051919050565b81810381811115610941577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610b6a57600080fd5b815161091981610a49565b600060208284031215610b8757600080fd5b8151801515811461091957600080fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e1461008657806366b00f68146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a13660046109cd565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a6e565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610ad6565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa6107c5565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610b05565b9050838110156103835760006103168286610b1e565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008473ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308873ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104139190610b58565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a79190610b05565b9050838110156105f2576105428673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104ff573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105239190610b58565b73ffffffffffffffffffffffffffffffffffffffff8716906000610800565b6105f28673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610590573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b49190610b58565b73ffffffffffffffffffffffffffffffffffffffff8716907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610800565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa15801561065f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106839190610b05565b9050848110156107bc5773ffffffffffffffffffffffffffffffffffffffff831663494666b6876106b48489610b1e565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b15801561071f57600080fd5b505af1925050508015610730575060015b6107bc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b50505050505050565b6000806107f360017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610b1e565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906108939086168361090b565b905061089e81610920565b610904576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c65640000000000006044820152606401610233565b5050505050565b606061091983600084610947565b9392505050565b60008151600014806109415750818060200190518101906109419190610b75565b92915050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516109719190610ad6565b60006040518083038185875af1925050503d80600081146109ae576040519150601f19603f3d011682016040523d82523d6000602084013e6109b3565b606091505b5092509050806109c557815160208301fd5b509392505050565b6000806000604084860312156109e257600080fd5b83359250602084013567ffffffffffffffff80821115610a0157600080fd5b818601915086601f830112610a1557600080fd5b813581811115610a2457600080fd5b876020828501011115610a3657600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a6b57600080fd5b50565b600080600080600060a08688031215610a8657600080fd5b8535610a9181610a49565b94506020860135610aa181610a49565b9350604086013592506060860135610ab881610a49565b91506080860135610ac881610a49565b809150509295509295909350565b6000825160005b81811015610af75760208186018101518583015201610add565b506000920191825250919050565b600060208284031215610b1757600080fd5b5051919050565b81810381811115610941577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610b6a57600080fd5b815161091981610a49565b600060208284031215610b8757600080fd5b8151801515811461091957600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/solidity/Solver.sol b/crates/contracts/solidity/Solver.sol index eece404458..cf6aea29ac 100644 --- a/crates/contracts/solidity/Solver.sol +++ b/crates/contracts/solidity/Solver.sol @@ -6,7 +6,6 @@ import { Interaction, Trade, ISettlement } from "./interfaces/ISettlement.sol"; import { Caller } from "./libraries/Caller.sol"; import { Math } from "./libraries/Math.sol"; import { SafeERC20 } from "./libraries/SafeERC20.sol"; -import { Spardose } from "./Spardose.sol"; import { Trader } from "./Trader.sol"; /// @title A contract for impersonating a solver. This contract @@ -71,16 +70,9 @@ contract Solver { settlementContract, sellToken, sellAmount, - nativeToken + nativeToken, + mock.spardose ); - - // Ensure that the user has sufficient sell token balance. In case - // balance overrides are enabled, the Spardose will fund the trader - // with simulated balances. - require( - Spardose(mock.spardose).ensureBalance(trader, sellToken, sellAmount), - "trader does not have enough sell token" - ); } // Warm the storage for sending ETH to smart contract addresses. diff --git a/crates/contracts/solidity/Spardose.sol b/crates/contracts/solidity/Spardose.sol index c1fa6167be..f453a0940e 100644 --- a/crates/contracts/solidity/Spardose.sol +++ b/crates/contracts/solidity/Spardose.sol @@ -12,21 +12,12 @@ import { SafeERC20 } from "./libraries/SafeERC20.sol"; contract Spardose { using SafeERC20 for *; - /// @dev Ensures that the trader has at least `amount` tokens. If not, it - /// will try and transfer the difference to the trader. + /// @dev Request funds from the piggy bank to be transferred to the caller. + /// Reverts if the transfer fails. /// - /// @param trader - the address of the trader - /// @param token - the token to ensure a balance for - /// @param amount - the amount of `token` that the `trader` must hold - /// - /// @return success - the `trader`'s `token` balance is more than `amount` - function ensureBalance(address trader, address token, uint256 amount) external returns (bool success) { - uint256 traderBalance = IERC20(token).balanceOf(trader); - if (traderBalance >= amount) { - return true; - } - - uint256 difference = amount - traderBalance; - return IERC20(token).trySafeTransfer(trader, difference); + /// @param token - the token request funds for + /// @param amount - the amount of `token` to transfer + function requestFunds(address token, uint256 amount) external { + IERC20(token).safeTransfer(msg.sender, amount); } } diff --git a/crates/contracts/solidity/Trader.sol b/crates/contracts/solidity/Trader.sol index c9fa5e0fdf..695bcdd4f1 100644 --- a/crates/contracts/solidity/Trader.sol +++ b/crates/contracts/solidity/Trader.sol @@ -6,6 +6,7 @@ import { Interaction, Trade, ISettlement } from "./interfaces/ISettlement.sol"; import { Caller } from "./libraries/Caller.sol"; import { Math } from "./libraries/Math.sol"; import { SafeERC20 } from "./libraries/SafeERC20.sol"; +import { Spardose } from "./Spardose.sol"; /// @title A contract for impersonating a trader. contract Trader { @@ -62,17 +63,19 @@ contract Trader { receive() external payable {} /// @dev Executes needed actions on behalf of the trader to make the trade possible. - /// (e.g. wrapping ETH and setting approvals) + /// (e.g. wrapping ETH, setting approvals, and funding the account) /// @param settlementContract - pass in settlement contract because it does not have /// a stable address in tests. /// @param sellToken - token being sold by the trade /// @param sellAmount - expected amount to be sold according to the quote /// @param nativeToken - ERC20 version of the chain's native token + /// @param spardose - piggy bank for requesting additional funds function prepareSwap( ISettlement settlementContract, address sellToken, uint256 sellAmount, - address nativeToken + address nativeToken, + address spardose ) external { require(!alreadyCalled(), "prepareSwap can only be called once"); @@ -80,11 +83,16 @@ contract Trader { uint256 availableNativeToken = IERC20(sellToken).balanceOf(address(this)); if (availableNativeToken < sellAmount) { uint256 amountToWrap = sellAmount - availableNativeToken; - require(address(this).balance >= amountToWrap, "not enough ETH to wrap"); - // Simulate wrapping the missing `ETH` so the user doesn't have to spend gas - // on that just to get a quote. If they are happy with the quote and want to - // create an order they will actually have to do the wrapping, though. - INativeERC20(nativeToken).deposit{value: amountToWrap}(); + // If the user has sufficient balance, simulate the wrapping the missing + // `ETH` so the user doesn't have to spend gas on that just to get a quote. + // If they are happy with the quote and want to create an order they will + // actually have to do the wrapping, though. Note that we don't attempt to + // wrap if the user doesn't have sufficient `ETH` balance, since that would + // revert. Instead, we fall-through so that we handle insufficient sell + // token balances uniformly for all tokens. + if (address(this).balance >= amountToWrap) { + INativeERC20(nativeToken).deposit{value: amountToWrap}(); + } } } @@ -99,6 +107,20 @@ contract Trader { IERC20(sellToken).safeApprove(address(settlementContract.vaultRelayer()), 0); IERC20(sellToken).safeApprove(address(settlementContract.vaultRelayer()), type(uint256).max); } + + // Ensure that the user has sufficient sell token balance. If not, request some + // funds from the Spardose (piggy bank) which will be available if balance + // overrides are enabled. + uint256 sellBalance = IERC20(sellToken).balanceOf(address(this)); + if (sellBalance < sellAmount) { + try Spardose(spardose).requestFunds(sellToken, sellAmount - sellBalance) {} + catch { + // The trader does not have sufficient sell token balance, and the + // piggy bank pre-fund failed, as balance overrides are not available. + // Revert with a helpful message. + revert("trader does not have enough sell token"); + } + } } /// @dev Validate all signature requests. This makes "signing" CoW protocol diff --git a/crates/contracts/solidity/libraries/SafeERC20.sol b/crates/contracts/solidity/libraries/SafeERC20.sol index 9cc30ef35f..1e5e7d708a 100644 --- a/crates/contracts/solidity/libraries/SafeERC20.sol +++ b/crates/contracts/solidity/libraries/SafeERC20.sol @@ -7,24 +7,19 @@ import { Caller } from "./Caller.sol"; library SafeERC20 { using Caller for *; - function trySafeTransfer(IERC20 self, address target, uint256 amount) internal returns (bool success) { + function safeTransfer(IERC20 self, address target, uint256 amount) internal { bytes memory cdata = abi.encodeCall(self.transfer, (target, amount)); - bytes memory rdata; - (success, rdata) = address(self).call(cdata); - return success && check(rdata); + bytes memory rdata = address(self).doCall(cdata); + require(check(rdata), "SafeERC20: transfer failed"); } function safeApprove(IERC20 self, address target, uint256 amount) internal { bytes memory cdata = abi.encodeCall(self.approve, (target, amount)); bytes memory rdata = address(self).doCall(cdata); - ensure(rdata, "SafeERC20: approval failed"); + require(check(rdata), "SafeERC20: approval failed"); } function check(bytes memory rdata) internal pure returns (bool ok) { return rdata.length == 0 || abi.decode(rdata, (bool)); } - - function ensure(bytes memory rdata, string memory message) internal pure { - require(check(rdata), message); - } } diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 2aafb0e98b..8af977edcf 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -10,7 +10,12 @@ use { number::nonzero::U256 as NonZeroU256, shared::{ price_estimation::{ - trade_verifier::{PriceQuery, TradeVerifier, TradeVerifying}, + trade_verifier::{ + balance_overrides::BalanceOverrides, + PriceQuery, + TradeVerifier, + TradeVerifying, + }, Estimate, Verification, }, @@ -37,6 +42,12 @@ async fn forked_node_bypass_verification_for_rfq_quotes() { .await; } +#[tokio::test] +#[ignore] +async fn local_node_verified_quote_eth_balance() { + run_test(verified_quote_eth_balance).await; +} + #[tokio::test] #[ignore] async fn local_node_verified_quote_for_settlement_contract() { @@ -70,7 +81,7 @@ async fn standard_verified_quote(web3: Web3) { let services = Services::new(&onchain).await; services.start_protocol(solver).await; - // quote where the trader has no balances or approval set. + // quote where the trader has sufficient balance and an approval set. let response = services .submit_quote(&OrderQuoteRequest { from: trader.address(), @@ -110,6 +121,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { web3.clone(), Arc::new(web3.clone()), Arc::new(web3.clone()), + Arc::new(BalanceOverrides::default()), block_stream, onchain.contracts().gp_settlement.address(), onchain.contracts().weth.address(), @@ -182,6 +194,51 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { ); } +/// Verified quotes work as for WETH trades without wrapping or approvals. +async fn verified_quote_eth_balance(web3: Web3) { + tracing::info!("Setting up chain state."); + let mut onchain = OnchainComponents::deploy(web3).await; + + let [solver] = onchain.make_solvers(to_wei(10)).await; + let [trader] = onchain.make_accounts(to_wei(1)).await; + let [token] = onchain + .deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000)) + .await; + let weth = &onchain.contracts().weth; + + tracing::info!("Starting services."); + let services = Services::new(&onchain).await; + services.start_protocol(solver).await; + + // quote where the trader has no WETH balances or approval set, but + // sufficient ETH for the trade + assert_eq!( + ( + weth.balance_of(trader.address()).call().await.unwrap(), + weth.allowance(trader.address(), onchain.contracts().allowance) + .call() + .await + .unwrap(), + ), + (U256::zero(), U256::zero()), + ); + let response = services + .submit_quote(&OrderQuoteRequest { + from: trader.address(), + sell_token: weth.address(), + buy_token: token.address(), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::BeforeFee { + value: to_wei(1).try_into().unwrap(), + }, + }, + ..Default::default() + }) + .await + .unwrap(); + assert!(response.verified); +} + /// Test that asserts that we can verify quotes where the settlement contract is /// the trader or receiver. async fn verified_quote_for_settlement_contract(web3: Web3) { @@ -271,30 +328,77 @@ async fn verified_quote_with_simulated_balance(web3: Web3) { let [token] = onchain .deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000)) .await; + let weth = &onchain.contracts().weth; tracing::info!("Starting services."); let services = Services::new(&onchain).await; services .start_protocol_with_args( ExtraServiceArgs { - api: vec![format!( + api: vec![ // The OpenZeppelin `ERC20Mintable` token uses a mapping in // the first (0'th) storage slot for balances. - "--quote-token-balance-overrides={:?}@0", - token.address() - )], + format!("--quote-token-balance-overrides={:?}@0", token.address()), + // We don't configure the WETH token and instead rely on + // auto-detection for balance overrides. + "--quote-autodetect-token-balance-overrides=true".to_string(), + ], ..Default::default() }, solver, ) .await; - // quote where the trader has no balances or approval set. + // quote where the trader has no balances or approval set from TOKEN->WETH + assert_eq!( + ( + token.balance_of(trader.address()).call().await.unwrap(), + token + .allowance(trader.address(), onchain.contracts().allowance) + .call() + .await + .unwrap(), + ), + (U256::zero(), U256::zero()), + ); let response = services .submit_quote(&OrderQuoteRequest { from: trader.address(), sell_token: token.address(), - buy_token: onchain.contracts().weth.address(), + buy_token: weth.address(), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::BeforeFee { + value: to_wei(1).try_into().unwrap(), + }, + }, + ..Default::default() + }) + .await + .unwrap(); + assert!(response.verified); + + // quote where the trader has no balances or approval set from WETH->TOKEN + assert_eq!( + ( + onchain + .web3() + .eth() + .balance(trader.address(), None) + .await + .unwrap(), + weth.balance_of(trader.address()).call().await.unwrap(), + weth.allowance(trader.address(), onchain.contracts().allowance) + .call() + .await + .unwrap(), + ), + (U256::zero(), U256::zero(), U256::zero()), + ); + let response = services + .submit_quote(&OrderQuoteRequest { + from: trader.address(), + sell_token: weth.address(), + buy_token: token.address(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: to_wei(1).try_into().unwrap(), diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 2d660adf03..b23f23385a 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -113,20 +113,19 @@ impl<'a> PriceEstimatorFactory<'a> { None => Arc::new(web3.clone()), }; - let balance_overrides = Arc::new(args.quote_token_balance_overrides.clone()); + let balance_overrides = args.balance_overrides.init(simulator.clone()); let verifier = TradeVerifier::new( web3, simulator, components.code_fetcher.clone(), + balance_overrides, network.block_stream.clone(), network.settlement, network.native_token, args.quote_inaccuracy_limit.clone(), ) - .await? - .with_balance_overrides(balance_overrides); - + .await?; Ok(Some(Arc::new(verifier))) } diff --git a/crates/shared/src/price_estimation/mod.rs b/crates/shared/src/price_estimation/mod.rs index 607925bc83..5db9810d01 100644 --- a/crates/shared/src/price_estimation/mod.rs +++ b/crates/shared/src/price_estimation/mod.rs @@ -1,5 +1,5 @@ use { - self::trade_verifier::balance_overrides::ConfigurationBalanceOverrides, + self::trade_verifier::balance_overrides, crate::{ arguments::{display_option, display_secret_option, ExternalSolver}, trade_finding::Interaction, @@ -213,16 +213,8 @@ pub struct Arguments { )] pub quote_timeout: Duration, - /// Token configuration for simulated balances on verified quotes. This - /// allows the quote verification system to produce verified quotes for - /// traders without sufficient balance for the configured token pairs. - /// - /// The expected format is a comma separated list of `${ADDR}@${SLOT}`, - /// where `ADDR` is the token address and `SLOT` is the Solidity storage - /// slot for the balances mapping. For example for WETH: - /// `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2@3`. - #[clap(long, env, default_value_t)] - pub quote_token_balance_overrides: ConfigurationBalanceOverrides, + #[clap(flatten)] + pub balance_overrides: balance_overrides::Arguments, } #[derive(clap::Parser)] @@ -302,7 +294,7 @@ impl Display for Arguments { quote_inaccuracy_limit, quote_verification, quote_timeout, - quote_token_balance_overrides, + balance_overrides, } = self; display_option( @@ -380,11 +372,7 @@ impl Display for Arguments { writeln!(f, "quote_inaccuracy_limit: {}", quote_inaccuracy_limit)?; writeln!(f, "quote_verification: {:?}", quote_verification)?; writeln!(f, "quote_timeout: {:?}", quote_timeout)?; - writeln!( - f, - "quote_token_balance_overrides: {:?}", - quote_token_balance_overrides - )?; + write!(f, "{}", balance_overrides)?; Ok(()) } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index fed26bb928..8d871f4656 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -1,11 +1,7 @@ pub mod balance_overrides; use { - self::balance_overrides::{ - BalanceOverrideRequest, - BalanceOverriding, - ConfigurationBalanceOverrides, - }, + self::balance_overrides::{BalanceOverrideRequest, BalanceOverriding}, super::{Estimate, Verification}, crate::{ code_fetching::CodeFetching, @@ -76,10 +72,12 @@ impl TradeVerifier { const SPARDOSE: H160 = addr!("0000000000000000000000000000000000020000"); const TRADER_IMPL: H160 = addr!("0000000000000000000000000000000000010000"); + #[allow(clippy::too_many_arguments)] pub async fn new( web3: Web3, simulator: Arc, code_fetcher: Arc, + balance_overrides: Arc, block_stream: CurrentBlockWatcher, settlement: H160, native_token: H160, @@ -91,7 +89,7 @@ impl TradeVerifier { Ok(Self { simulator, code_fetcher, - balance_overrides: Arc::new(ConfigurationBalanceOverrides::default()), + balance_overrides, block_stream, settlement: settlement_contract, native_token, @@ -101,11 +99,6 @@ impl TradeVerifier { }) } - pub fn with_balance_overrides(mut self, balance_overrides: Arc) -> Self { - self.balance_overrides = balance_overrides; - self - } - async fn verify_inner( &self, query: &PriceQuery, @@ -344,23 +337,24 @@ impl TradeVerifier { // not alter solver balances which may be used during settlement. We use // a similar strategy for determining whether or not to set approvals on // behalf of the trader. - if let Some(solver_balance_override) = - self.balance_overrides - .state_override(&BalanceOverrideRequest { - token: query.sell_token, - holder: Self::SPARDOSE, - amount: match query.kind { - OrderKind::Sell => query.in_amount.get(), - OrderKind::Buy => trade.out_amount( - &query.buy_token, - &query.sell_token, - &query.in_amount.get(), - &query.kind, - )?, - }, - }) + if let Some(solver_balance_override) = self + .balance_overrides + .state_override(BalanceOverrideRequest { + token: query.sell_token, + holder: Self::SPARDOSE, + amount: match query.kind { + OrderKind::Sell => query.in_amount.get(), + OrderKind::Buy => trade.out_amount( + &query.buy_token, + &query.sell_token, + &query.in_amount.get(), + &query.kind, + )?, + }, + }) + .await { - tracing::debug!(?solver_balance_override, "solver balance override enabled"); + tracing::trace!(?solver_balance_override, "solver balance override enabled"); overrides.insert(query.sell_token, solver_balance_override); } diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs index ae7550f9a2..d1f85c03d3 100644 --- a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs @@ -1,7 +1,10 @@ mod detector; use { + self::detector::{DetectionError, Detector}, + crate::code_simulation::CodeSimulating, anyhow::Context as _, + cached::{Cached, SizedCache}, ethcontract::{Address, H256, U256}, ethrpc::extensions::StateOverride, maplit::hashmap, @@ -9,16 +12,132 @@ use { collections::HashMap, fmt::{self, Display, Formatter}, str::FromStr, + sync::{Arc, Mutex}, }, web3::signing, }; +/// Balance override configuration arguments. +#[derive(clap::Parser)] +#[group(skip)] +pub struct Arguments { + /// Token configuration for simulated balances on verified quotes. This + /// allows the quote verification system to produce verified quotes for + /// traders without sufficient balance for the configured token pairs. + /// + /// The expected format is a comma separated list of `${ADDR}@${SLOT}`, + /// where `ADDR` is the token address and `SLOT` is the Solidity storage + /// slot for the balances mapping. For example for WETH: + /// `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2@3`. + #[clap(long, env, default_value_t)] + pub quote_token_balance_overrides: TokenConfiguration, + + /// Enable automatic detection of token balance overrides. Note that + /// pre-configured values with the `--quote-token-balance-overrides` flag + /// will take precedence. + #[clap(long, env, action = clap::ArgAction::Set, default_value_t)] + pub quote_autodetect_token_balance_overrides: bool, +} + +impl Arguments { + const CACHE_SIZE: usize = 1000; + + /// Creates a balance overrides instance from the current configuration. + pub fn init(&self, simulator: Arc) -> Arc { + Arc::new(BalanceOverrides { + hardcoded: self.quote_token_balance_overrides.0.clone(), + detector: self.quote_autodetect_token_balance_overrides.then(|| { + ( + Detector::new(simulator), + Mutex::new(SizedCache::with_size(Self::CACHE_SIZE)), + ) + }), + }) + } +} + +impl Display for Arguments { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let Self { + quote_token_balance_overrides, + quote_autodetect_token_balance_overrides, + } = self; + + writeln!( + f, + "quote_token_balance_overrides: {:?}", + quote_token_balance_overrides + )?; + writeln!( + f, + "quote_autodetect_token_balance_overrides: {:?}", + quote_autodetect_token_balance_overrides + )?; + + Ok(()) + } +} + +/// Token configurations for the `BalanceOverriding` component. +#[derive(Clone, Debug, Default)] +pub struct TokenConfiguration(HashMap); + +impl Display for TokenConfiguration { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let format_entry = + |f: &mut Formatter, (addr, strategy): (&Address, &Strategy)| match strategy { + Strategy::Mapping { slot } => write!(f, "{addr:?}@{slot}"), + }; + + let mut entries = self.0.iter(); + + let Some(first) = entries.next() else { + return Ok(()); + }; + format_entry(f, first)?; + + for entry in entries { + f.write_str(",")?; + format_entry(f, entry)?; + } + + Ok(()) + } +} + +impl FromStr for TokenConfiguration { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Ok(Self::default()); + } + + let entries = s + .split(',') + .map(|part| -> Result<_, Self::Err> { + let (addr, slot) = part + .split_once('@') + .context("expected {addr}@{slot} format")?; + Ok(( + addr.parse()?, + Strategy::Mapping { + slot: slot.parse()?, + }, + )) + }) + .collect::>()?; + Ok(Self(entries)) + } +} + /// A component that can provide balance overrides for tokens. /// /// This allows a wider range of verified quotes to work, even when balances /// are not available for the quoter. +#[async_trait::async_trait] pub trait BalanceOverriding: Send + Sync + 'static { - fn state_override(&self, request: &BalanceOverrideRequest) -> Option; + async fn state_override(&self, request: BalanceOverrideRequest) -> Option; } /// Parameters for computing a balance override request. @@ -31,10 +150,6 @@ pub struct BalanceOverrideRequest { pub amount: U256, } -/// A simple configuration-based balance override provider. -#[derive(Clone, Debug, Default)] -pub struct ConfigurationBalanceOverrides(HashMap); - /// Balance override strategy for a token. #[derive(Clone, Debug)] pub enum Strategy { @@ -71,69 +186,73 @@ impl Strategy { } } -impl ConfigurationBalanceOverrides { - pub fn new(config: HashMap) -> Self { - Self(config) - } -} +type DetectorCache = Mutex>>; -impl BalanceOverriding for ConfigurationBalanceOverrides { - fn state_override(&self, request: &BalanceOverrideRequest) -> Option { - let strategy = self.0.get(&request.token)?; - let (key, value) = strategy.state_override(&request.holder, &request.amount); - Some(StateOverride { - state_diff: Some(hashmap! { key => value }), - ..Default::default() - }) - } +/// The default balance override provider. +#[derive(Debug, Default)] +pub struct BalanceOverrides { + /// The configured balance override strategies for tokens. + /// + /// These take priority over the auto-detection mechanism and are excluded + /// from the cache in order to prevent them from getting cleaned up by + /// the caching policy. + hardcoded: HashMap, + /// The balance override detector and its cache. Set to `None` if + /// auto-detection is not enabled. + detector: Option<(Detector, DetectorCache)>, } -impl Display for ConfigurationBalanceOverrides { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let format_entry = - |f: &mut Formatter, (addr, strategy): (&Address, &Strategy)| match strategy { - Strategy::Mapping { slot } => write!(f, "{addr:?}@{slot}"), - }; +impl BalanceOverrides { + async fn cached_detection(&self, token: Address) -> Option { + let (detector, cache) = self.detector.as_ref()?; + tracing::trace!(?token, "attempting to auto-detect"); - let mut entries = self.0.iter(); + { + let mut cache = cache.lock().unwrap(); + if let Some(strategy) = cache.cache_get(&token) { + tracing::trace!(?token, "cache hit"); + return strategy.clone(); + } + } - let Some(first) = entries.next() else { - return Ok(()); - }; - format_entry(f, first)?; + let strategy = detector.detect(token).await; - for entry in entries { - f.write_str(",")?; - format_entry(f, entry)?; + // Only cache when we successfully detect the token, or we can't find + // it. Anything else is likely a temporary simulator (i.e. node) failure + // which we don't want to cache. + if matches!(&strategy, Ok(_) | Err(DetectionError::NotFound)) { + tracing::debug!(?token, ?strategy, "caching auto-detected strategy"); + let cached_strategy = strategy.as_ref().ok().cloned(); + cache.lock().unwrap().cache_set(token, cached_strategy); + } else { + tracing::warn!( + ?token, + ?strategy, + "error auto-detecting token balance override strategy" + ); } - Ok(()) + strategy.ok() } } -impl FromStr for ConfigurationBalanceOverrides { - type Err = anyhow::Error; +#[async_trait::async_trait] +impl BalanceOverriding for BalanceOverrides { + async fn state_override(&self, request: BalanceOverrideRequest) -> Option { + let strategy = if let Some(strategy) = self.hardcoded.get(&request.token) { + tracing::trace!(token = ?request.token, "using pre-configured balance override strategy"); + Some(strategy.clone()) + } else { + self.cached_detection(request.token).await + }?; - fn from_str(s: &str) -> Result { - if s.is_empty() { - return Ok(Self::default()); - } + let (key, value) = strategy.state_override(&request.holder, &request.amount); + tracing::trace!(?strategy, ?key, ?value, "overriding token balance"); - let entries = s - .split(',') - .map(|part| -> Result<_, Self::Err> { - let (addr, slot) = part - .split_once('@') - .context("expected {addr}@{slot} format")?; - Ok(( - addr.parse()?, - Strategy::Mapping { - slot: slot.parse()?, - }, - )) - }) - .collect::>()?; - Ok(Self(entries)) + Some(StateOverride { + state_diff: Some(hashmap! { key => value }), + ..Default::default() + }) } } @@ -141,20 +260,25 @@ impl FromStr for ConfigurationBalanceOverrides { mod tests { use {super::*, hex_literal::hex}; - #[test] - fn balance_override_computation() { - let balance_overrides = ConfigurationBalanceOverrides::new(hashmap! { - addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB") => Strategy::Mapping { - slot: U256::from(0), + #[tokio::test] + async fn balance_override_computation() { + let balance_overrides = BalanceOverrides { + hardcoded: hashmap! { + addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB") => Strategy::Mapping { + slot: U256::from(0), + }, }, - }); + ..Default::default() + }; assert_eq!( - balance_overrides.state_override(&BalanceOverrideRequest { - token: addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"), - holder: addr!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"), - amount: 0x42_u64.into(), - }), + balance_overrides + .state_override(BalanceOverrideRequest { + token: addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"), + holder: addr!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"), + amount: 0x42_u64.into(), + }) + .await, Some(StateOverride { state_diff: Some(hashmap! { H256(hex!("fca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33")) => @@ -189,15 +313,17 @@ mod tests { // ``` } - #[test] - fn balance_overrides_none_for_unknown_tokens() { - let balance_overrides = ConfigurationBalanceOverrides::default(); + #[tokio::test] + async fn balance_overrides_none_for_unknown_tokens() { + let balance_overrides = BalanceOverrides::default(); assert_eq!( - balance_overrides.state_override(&BalanceOverrideRequest { - token: addr!("0000000000000000000000000000000000000000"), - holder: addr!("0000000000000000000000000000000000000001"), - amount: U256::zero(), - }), + balance_overrides + .state_override(BalanceOverrideRequest { + token: addr!("0000000000000000000000000000000000000000"), + holder: addr!("0000000000000000000000000000000000000001"), + amount: U256::zero(), + }) + .await, None, ); } diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs index 0ce48951e3..d8795b7ce4 100644 --- a/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs @@ -5,7 +5,10 @@ use { ethcontract::{Address, U256}, ethrpc::extensions::StateOverride, maplit::hashmap, - std::sync::Arc, + std::{ + fmt::{self, Debug, Formatter}, + sync::Arc, + }, thiserror::Error, web3::{signing::keccak256, types::CallRequest}, }; @@ -13,11 +16,11 @@ use { /// A heuristic balance override detector based on `eth_call` simulations. /// /// This has the exact same node requirements as trade verification. +#[derive(Clone)] pub struct Detector { simulator: Arc, } -#[allow(dead_code)] impl Detector { /// Number of different slots to try out. const TRIES: u8 = 25; @@ -90,6 +93,14 @@ impl Detector { } } +impl Debug for Detector { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("Detector") + .field("simulator", &format_args!("Arc")) + .finish() + } +} + /// An error detecting the balance override strategy for a token. #[derive(Debug, Error)] pub enum DetectionError { From 78a1b81c5da367fddcb1bf27f7cf9d9b3001fa20 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Fri, 6 Dec 2024 17:44:38 +0100 Subject: [PATCH 08/25] Verify quotes from zero address (#3150) # Description @nlordell's recent PR stack enabled the use case where a real trader (wallet is connected in the frontend) does not have the required balances by faking balances. But with the new ability to fake balances for many tokens we can even go one step further. # Changes If we know how to fake balances for the given sell token AND no wallet is connected (i.e. `verification.from.is_zero()`) we simply generate a random address and use that as the trader with fake balances. Using a non-zero address is important because many token transfer functions don't like sending tokens to or from the zero address. This should prevent the annoying UX where you sometime see a really good (unverified) price when your wallet is not connected and end up seeing worse (but verified) prices after you connect your wallet. ## How to test added an e2e test which produces a verified quote with `from: H160::zero()` --- crates/e2e/tests/e2e/quote_verification.rs | 18 +++ .../src/price_estimation/trade_verifier.rs | 110 ++++++++++-------- 2 files changed, 78 insertions(+), 50 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 8af977edcf..149afbfc7d 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -409,4 +409,22 @@ async fn verified_quote_with_simulated_balance(web3: Web3) { .await .unwrap(); assert!(response.verified); + + // with balance overrides we can even verify quotes for the 0 address + // which is used when no wallet is connected in the frontend + let response = services + .submit_quote(&OrderQuoteRequest { + from: H160::zero(), + sell_token: weth.address(), + buy_token: token.address(), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::BeforeFee { + value: to_wei(1).try_into().unwrap(), + }, + }, + ..Default::default() + }) + .await + .unwrap(); + assert!(response.verified); } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 8d871f4656..496cecdd4f 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -25,7 +25,6 @@ use { }, ethcontract::{tokens::Tokenize, Bytes, H160, U256}, ethrpc::{block_stream::CurrentBlockWatcher, extensions::StateOverride, Web3}, - maplit::hashmap, model::{ order::{OrderData, OrderKind, BUY_ETH_ADDRESS}, signature::{Signature, SigningScheme}, @@ -102,17 +101,20 @@ impl TradeVerifier { async fn verify_inner( &self, query: &PriceQuery, - verification: &Verification, + mut verification: Verification, trade: &TradeKind, out_amount: &U256, ) -> Result { - if verification.from.is_zero() { - // Don't waste time on common simulations which will always fail. - return Err(anyhow::anyhow!("trader is zero address").into()); - } - let start = std::time::Instant::now(); + // this may change the `verification` parameter (to make more + // quotes verifiable) so we do it as the first thing to ensure + // that all the following code uses the updated value + let overrides = self + .prepare_state_overrides(&mut verification, query, trade) + .await + .map_err(Error::SimulationFailed)?; + // Use `tx_origin` if response indicates that a special address is needed for // the simulation to pass. Otherwise just use the solver address. let solver = trade.tx_origin().unwrap_or(trade.solver()); @@ -132,7 +134,7 @@ impl TradeVerifier { let settlement = encode_settlement( query, - verification, + &verification, trade, &tokens, &clearing_prices, @@ -141,7 +143,7 @@ impl TradeVerifier { &self.domain_separator, )?; - let settlement = add_balance_queries(settlement, query, verification, &solver); + let settlement = add_balance_queries(settlement, query, &verification, &solver); let settlement = self .settlement @@ -191,11 +193,6 @@ impl TradeVerifier { ..Default::default() }; - let overrides = self - .prepare_state_overrides(verification, query, trade) - .await - .map_err(Error::SimulationFailed)?; - let output = self .simulator .simulate(call, overrides, Some(block.number)) @@ -287,17 +284,60 @@ impl TradeVerifier { /// trade. async fn prepare_state_overrides( &self, - verification: &Verification, + verification: &mut Verification, query: &PriceQuery, trade: &TradeKind, ) -> Result> { + let mut overrides = HashMap::with_capacity(6); + + // Provide mocked balances if possible to the spardose to allow it to + // give some balances to the trader in order to verify trades even for + // owners without balances. Note that we use a separate account for + // funding to not interfere with the settlement process. This allows the + // simulation to conditionally transfer the balance only when it is + // safe to mock the trade pre-conditions on behalf of the user and to + // not alter solver balances which may be used during settlement. We use + // a similar strategy for determining whether or not to set approvals on + // behalf of the trader. + if let Some(solver_balance_override) = self + .balance_overrides + .state_override(BalanceOverrideRequest { + token: query.sell_token, + holder: Self::SPARDOSE, + amount: match query.kind { + OrderKind::Sell => query.in_amount.get(), + OrderKind::Buy => trade.out_amount( + &query.buy_token, + &query.sell_token, + &query.in_amount.get(), + &query.kind, + )?, + }, + }) + .await + { + tracing::trace!(?solver_balance_override, "solver balance override enabled"); + overrides.insert(query.sell_token, solver_balance_override); + + if verification.from.is_zero() { + verification.from = H160::random(); + tracing::debug!( + trader = ?verification.from, + "use random trader address with fake balances" + ); + } + } else if verification.from.is_zero() { + anyhow::bail!("trader is zero address and balances can not be faked"); + } + // Set up mocked trader. - let mut overrides = hashmap! { - verification.from => StateOverride { + overrides.insert( + verification.from, + StateOverride { code: Some(deployed_bytecode!(Trader)), ..Default::default() }, - }; + ); // If the trader is a smart contract we also need to store its implementation // to proxy into it during the simulation. @@ -328,36 +368,6 @@ impl TradeVerifier { }, ); - // Provide mocked balances if possible to the spardose to allow it to - // give some balances to the trader in order to verify trades even for - // owners without balances. Note that we use a separate account for - // funding to not interfere with the settlement process. This allows the - // simulation to conditionally transfer the balance only when it is - // safe to mock the trade pre-conditions on behalf of the user and to - // not alter solver balances which may be used during settlement. We use - // a similar strategy for determining whether or not to set approvals on - // behalf of the trader. - if let Some(solver_balance_override) = self - .balance_overrides - .state_override(BalanceOverrideRequest { - token: query.sell_token, - holder: Self::SPARDOSE, - amount: match query.kind { - OrderKind::Sell => query.in_amount.get(), - OrderKind::Buy => trade.out_amount( - &query.buy_token, - &query.sell_token, - &query.in_amount.get(), - &query.kind, - )?, - }, - }) - .await - { - tracing::trace!(?solver_balance_override, "solver balance override enabled"); - overrides.insert(query.sell_token, solver_balance_override); - } - // Set up mocked solver. let mut solver_override = StateOverride { code: Some(deployed_bytecode!(Solver)), @@ -408,7 +418,7 @@ impl TradeVerifying for TradeVerifier { ) .context("failed to compute trade out amount")?; match self - .verify_inner(query, verification, &trade, &out_amount) + .verify_inner(query, verification.clone(), &trade, &out_amount) .await { Ok(verified) => Ok(verified), @@ -790,7 +800,7 @@ enum Error { #[cfg(test)] mod tests { - use {super::*, std::str::FromStr}; + use {super::*, maplit::hashmap, std::str::FromStr}; #[test] fn discards_inaccurate_quotes() { From c47c80c0062b515b24b3db3e5dad4a203a8996d3 Mon Sep 17 00:00:00 2001 From: m-lord-renkse <160488334+m-lord-renkse@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:57:04 +0100 Subject: [PATCH 09/25] Add is_winner to solver competition endpoints (#3127) # Description Add is_winner to solver competition endpoints # Changes Add is_winner boolean to each solution json object. ## How to test 1. e2e test 2. Regression test ## Related Issues Fixes #3070 --- crates/autopilot/src/run_loop.rs | 1 + crates/e2e/tests/e2e/solver_competition.rs | 2 ++ crates/model/src/solver_competition.rs | 19 ++++++++++++++----- crates/orderbook/openapi.yml | 5 ++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/autopilot/src/run_loop.rs b/crates/autopilot/src/run_loop.rs index 6228d81fcd..e86692ff4e 100644 --- a/crates/autopilot/src/run_loop.rs +++ b/crates/autopilot/src/run_loop.rs @@ -476,6 +476,7 @@ impl RunLoop { .iter() .map(|(token, price)| (token.0, price.get().into())) .collect(), + is_winner: participant.is_winner(), }) .collect(), }; diff --git a/crates/e2e/tests/e2e/solver_competition.rs b/crates/e2e/tests/e2e/solver_competition.rs index b2428ac1cd..8ed1936923 100644 --- a/crates/e2e/tests/e2e/solver_competition.rs +++ b/crates/e2e/tests/e2e/solver_competition.rs @@ -126,8 +126,10 @@ async fn solver_competition(web3: Web3) { // Non winning candidate assert!(competition.common.solutions[0].ranking == 2); + assert!(!competition.common.solutions[0].is_winner); // Winning candidate assert!(competition.common.solutions[1].ranking == 1); + assert!(competition.common.solutions[1].is_winner); } async fn fairness_check(web3: Web3) { diff --git a/crates/model/src/solver_competition.rs b/crates/model/src/solver_competition.rs index 6fba23d1ed..ddfdf0a941 100644 --- a/crates/model/src/solver_competition.rs +++ b/crates/model/src/solver_competition.rs @@ -52,6 +52,8 @@ pub struct SolverSettlement { #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub clearing_prices: BTreeMap, pub orders: Vec, + #[serde(default)] + pub is_winner: bool, } #[serde_as] @@ -165,6 +167,7 @@ mod tests { "executedAmount": "14", } ], + "isWinner": true, }, ], }); @@ -206,6 +209,7 @@ mod tests { executed_amount: 14.into(), }, ], + is_winner: true, }], }, }; @@ -271,7 +275,8 @@ mod tests { "clearingPrices": { "0x9d409a0a012cfba9b15f6d4b36ac57a46966ab9a": "32666943622", "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "19827747502258423744093" - } + }, + "isWinner": true, }, { "orders": [ @@ -292,7 +297,8 @@ mod tests { "clearingPrices": { "0x9d409a0a012cfba9b15f6d4b36ac57a46966ab9a": "32652483021", "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "19827747502258423744093" - } + }, + "isWinner": false, }, { "orders": [ @@ -313,7 +319,8 @@ mod tests { "clearingPrices": { "0x9d409a0a012cfba9b15f6d4b36ac57a46966ab9a": "100000", "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "60721701581190944" - } + }, + "isWinner": false, }, { "orders": [ @@ -334,7 +341,8 @@ mod tests { "clearingPrices": { "0x9d409a0a012cfba9b15f6d4b36ac57a46966ab9a": "32725026283", "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "19827747502258423744093" - } + }, + "isWinner": false, }, { "orders": [ @@ -355,7 +363,8 @@ mod tests { "clearingPrices": { "0x9d409a0a012cfba9b15f6d4b36ac57a46966ab9a": "32752835446", "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "19827747502258423744093" - } + }, + "isWinner": false, } ], "transactionHashes": ["0x044499c2a830890cb0a8ecf9aec6c5621e8310092a58d369cdef726254d3d108"], diff --git a/crates/orderbook/openapi.yml b/crates/orderbook/openapi.yml index 97a4f046a1..c26608a911 100644 --- a/crates/orderbook/openapi.yml +++ b/crates/orderbook/openapi.yml @@ -403,7 +403,7 @@ paths: "200": description: Version content: - text/plain: {} + text/plain: { } "/api/v1/app_data/{app_data_hash}": get: summary: Get the full `appData` from contract `appDataHash`. @@ -1661,6 +1661,9 @@ components: $ref: "#/components/schemas/UID" executedAmount: $ref: "#/components/schemas/BigUint" + isWinner: + type: boolean + description: whether the solution is a winner (received the right to get executed) or not NativePriceResponse: description: | The estimated native price for the token From 2bb146f4732937ad6667bfa5fe5140aabb98f6b1 Mon Sep 17 00:00:00 2001 From: Dusan Stanivukovic Date: Tue, 10 Dec 2024 11:21:34 +0100 Subject: [PATCH 10/25] Reveal and settle to receive integer solution id (#3152) # Description A follow up for https://github.com/cowprotocol/services/pull/3071 This PR allows reference driver to receive both integer and strings for solution id, on `/reveal` and `/settle` endpoints. Note how this PR implements task 2 from the [linked pull request](https://github.com/cowprotocol/services/pull/3071). This is done in this PR as a separate step to give time to colocated solvers using reference driver to update on time. If we were to just switch to using integer, colocated drivers would incur downtime. Next step is final step to use integer ONLY in both autopilot and driver, once we made sure all colocated solvers updated their driver. # Changes - [ ] Driver able to receive both integer and string on `/reveal` and `/settle` - [ ] ... ## How to test The code is copy paste of already tested code. --- crates/driver/src/infra/api/routes/mod.rs | 31 +++++++++++++++++++ .../infra/api/routes/reveal/dto/solution.rs | 4 +-- .../infra/api/routes/settle/dto/solution.rs | 4 +-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/driver/src/infra/api/routes/mod.rs b/crates/driver/src/infra/api/routes/mod.rs index ee1027b0e9..434846385e 100644 --- a/crates/driver/src/infra/api/routes/mod.rs +++ b/crates/driver/src/infra/api/routes/mod.rs @@ -15,3 +15,34 @@ pub(super) use { settle::settle, solve::{solve, AuctionError}, }; + +pub(crate) fn deserialize_solution_id<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + struct SolutionIdVisitor; + + impl serde::de::Visitor<'_> for SolutionIdVisitor { + type Value = u64; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string or integer representing a solution ID") + } + + fn visit_u64(self, value: u64) -> Result + where + E: serde::de::Error, + { + Ok(value) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + value.parse::().map_err(serde::de::Error::custom) + } + } + + deserializer.deserialize_any(SolutionIdVisitor) +} diff --git a/crates/driver/src/infra/api/routes/reveal/dto/solution.rs b/crates/driver/src/infra/api/routes/reveal/dto/solution.rs index 77a80e28b1..0c8d55fa49 100644 --- a/crates/driver/src/infra/api/routes/reveal/dto/solution.rs +++ b/crates/driver/src/infra/api/routes/reveal/dto/solution.rs @@ -1,11 +1,11 @@ -use {serde::Deserialize, serde_with::serde_as}; +use {super::super::super::deserialize_solution_id, serde::Deserialize, serde_with::serde_as}; #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Solution { /// Unique ID of the solution (per driver competition), to reveal. - #[serde_as(as = "serde_with::DisplayFromStr")] + #[serde(deserialize_with = "deserialize_solution_id")] pub solution_id: u64, /// Auction ID in which the specified solution ID is competing. #[serde_as(as = "Option")] diff --git a/crates/driver/src/infra/api/routes/settle/dto/solution.rs b/crates/driver/src/infra/api/routes/settle/dto/solution.rs index cbe0441ac3..6c62cd0cdd 100644 --- a/crates/driver/src/infra/api/routes/settle/dto/solution.rs +++ b/crates/driver/src/infra/api/routes/settle/dto/solution.rs @@ -1,11 +1,11 @@ -use {serde::Deserialize, serde_with::serde_as}; +use {super::super::super::deserialize_solution_id, serde::Deserialize, serde_with::serde_as}; #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Solution { /// Unique ID of the solution (per driver competition), to settle. - #[serde_as(as = "serde_with::DisplayFromStr")] + #[serde(deserialize_with = "deserialize_solution_id")] pub solution_id: u64, /// The last block number in which the solution TX can be included pub submission_deadline_latest_block: u64, From 665e4015a8b287f0115b5a696264ab00bf3db188 Mon Sep 17 00:00:00 2001 From: Dusan Stanivukovic Date: Tue, 10 Dec 2024 16:15:27 +0100 Subject: [PATCH 11/25] [TRIVIAL] Remove enable_multiple_fees config (#3155) # Description This config was added for one purpose only - to coordinate with accounting team when and how to turn on the feature - reference https://github.com/cowprotocol/services/pull/2595#pullrequestreview-2001563041 Since battled tested, this can be removed now. Will create the PR for infrastructure as well. Edit: https://github.com/cowprotocol/infrastructure/pull/2394 # Changes - [ ] Remove `enable_multiple_fees` ## How to test e2e tests --- crates/autopilot/src/arguments.rs | 6 ----- crates/autopilot/src/domain/fee/mod.rs | 35 ++------------------------ crates/autopilot/src/run.rs | 6 +---- crates/e2e/tests/e2e/limit_orders.rs | 1 - crates/e2e/tests/e2e/protocol_fee.rs | 1 - 5 files changed, 3 insertions(+), 46 deletions(-) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index 1a8420973e..677237ea84 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -181,10 +181,6 @@ pub struct Arguments { #[clap(long, env, use_value_delimiter = true)] pub fee_policies: Vec, - /// Enables multiple fees - #[clap(long, env, action = clap::ArgAction::Set, default_value = "false")] - pub enable_multiple_fees: bool, - /// Maximum partner fee allow. If the partner fee specified is greater than /// this maximum, the partner fee will be capped #[clap(long, env, default_value = "0.01")] @@ -269,7 +265,6 @@ impl std::fmt::Display for Arguments { shadow, solve_deadline, fee_policies, - enable_multiple_fees, fee_policy_max_partner_fee, order_events_cleanup_interval, order_events_cleanup_threshold, @@ -326,7 +321,6 @@ impl std::fmt::Display for Arguments { display_option(f, "shadow", shadow)?; writeln!(f, "solve_deadline: {:?}", solve_deadline)?; writeln!(f, "fee_policies: {:?}", fee_policies)?; - writeln!(f, "enable_multiple_fees: {:?}", enable_multiple_fees)?; writeln!( f, "fee_policy_max_partner_fee: {:?}", diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index ce18628af7..f40c170b25 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -14,7 +14,6 @@ use { }, app_data::Validator, derive_more::Into, - itertools::Itertools, primitive_types::{H160, U256}, prometheus::core::Number, std::{collections::HashSet, str::FromStr}, @@ -57,14 +56,12 @@ pub type ProtocolFeeExemptAddresses = HashSet; pub struct ProtocolFees { fee_policies: Vec, max_partner_fee: FeeFactor, - enable_protocol_fees: bool, } impl ProtocolFees { pub fn new( fee_policies: &[arguments::FeePolicy], fee_policy_max_partner_fee: FeeFactor, - enable_protocol_fees: bool, ) -> Self { Self { fee_policies: fee_policies @@ -73,7 +70,6 @@ impl ProtocolFees { .map(ProtocolFee::from) .collect(), max_partner_fee: fee_policy_max_partner_fee, - enable_protocol_fees, } } @@ -132,37 +128,10 @@ impl ProtocolFees { fee: quote.fee.into(), }; - if self.enable_protocol_fees { - self.apply_multiple_policies(order, quote, order_, quote_, partner_fee) - } else { - self.apply_single_policy(order, quote, order_, quote_, partner_fee) - } - } - - fn apply_single_policy( - &self, - order: boundary::Order, - quote: domain::Quote, - order_: boundary::Amounts, - quote_: boundary::Amounts, - partner_fees: Vec, - ) -> domain::Order { - if let Some(partner_fee) = partner_fees.first() { - return boundary::order::to_domain(order, vec![*partner_fee], Some(quote)); - } - let protocol_fees = self - .fee_policies - .iter() - .find_map(|fee_policy| { - Self::protocol_fee_into_policy(&order, &order_, "e_, fee_policy) - }) - .and_then(|policy| Self::variant_fee_apply(&order, "e, policy)) - .into_iter() - .collect_vec(); - boundary::order::to_domain(order, protocol_fees, Some(quote)) + self.apply_policies(order, quote, order_, quote_, partner_fee) } - fn apply_multiple_policies( + fn apply_policies( &self, order: boundary::Order, quote: domain::Quote, diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 59abed8253..2ce897edc8 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -439,11 +439,7 @@ pub async fn run(args: Arguments) { args.limit_order_price_factor .try_into() .expect("limit order price factor can't be converted to BigDecimal"), - domain::ProtocolFees::new( - &args.fee_policies, - args.fee_policy_max_partner_fee, - args.enable_multiple_fees, - ), + domain::ProtocolFees::new(&args.fee_policies, args.fee_policy_max_partner_fee), cow_amm_registry.clone(), args.run_loop_native_price_timeout, ); diff --git a/crates/e2e/tests/e2e/limit_orders.rs b/crates/e2e/tests/e2e/limit_orders.rs index c1819db63b..94eb7e9e21 100644 --- a/crates/e2e/tests/e2e/limit_orders.rs +++ b/crates/e2e/tests/e2e/limit_orders.rs @@ -901,7 +901,6 @@ async fn no_liquidity_limit_order(web3: Web3) { ExtraServiceArgs { autopilot: vec![ protocol_fees_config, - "--enable-multiple-fees=true".to_string(), format!("--unsupported-tokens={:#x}", unsupported.address()), ], ..Default::default() diff --git a/crates/e2e/tests/e2e/protocol_fee.rs b/crates/e2e/tests/e2e/protocol_fee.rs index c565c0077d..ffd65e9d26 100644 --- a/crates/e2e/tests/e2e/protocol_fee.rs +++ b/crates/e2e/tests/e2e/protocol_fee.rs @@ -120,7 +120,6 @@ async fn combined_protocol_fees(web3: Web3) { let autopilot_config = vec![ ProtocolFeesConfig(vec![limit_surplus_policy, market_price_improvement_policy]).to_string(), "--fee-policy-max-partner-fee=0.02".to_string(), - "--enable-multiple-fees=true".to_string(), ]; let services = Services::new(&onchain).await; services From 2ad85a327b3d1f4c551afac37037e2466674dd36 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Wed, 11 Dec 2024 10:18:47 +0100 Subject: [PATCH 12/25] [EASY] Improve logging for submitting transactions (#3158) # Description There was a wish to associate MEV blocker request logs with transactions sent by the driver. This is currently not possible for 2 reasons: 1. there is no log right after submitting the tx so associating logs by timestamp is not possible 2. the `/settle` handler spawns a new task to make sure transactions get cancelled (if needed) even when the autopilot terminates the request. Unfortunately how we pass around `request_ids` (which could also be used to associated these logs) is very convoluted and was overlooked here. Since the spawned task has its request_id variable not populated it generates a new id which does not match the original id. # Changes 1. add log right after submitting a tx. Additional tracing information we get by default are `request_id`, `solver` and `mempool` type 2. spawn `/settle` task in a way that preserves the original `request_id` Reference slack [thread](https://cowservices.slack.com/archives/C0375NV72SC/p1733768490743619) --- crates/driver/src/domain/mempools.rs | 1 + .../driver/src/infra/api/routes/settle/mod.rs | 8 +++++--- crates/observe/src/request_id.rs | 17 ++++++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/crates/driver/src/domain/mempools.rs b/crates/driver/src/domain/mempools.rs index 9872fd7f25..0d5df67404 100644 --- a/crates/driver/src/domain/mempools.rs +++ b/crates/driver/src/domain/mempools.rs @@ -103,6 +103,7 @@ impl Mempools { block_stream.next().await; let hash = mempool.submit(tx.clone(), settlement.gas, solver).await?; + tracing::debug!(?hash, "submitted tx to the mempool"); // Wait for the transaction to be mined, expired or failing. let result = async { diff --git a/crates/driver/src/infra/api/routes/settle/mod.rs b/crates/driver/src/infra/api/routes/settle/mod.rs index b75bdb7435..6a169ce3b1 100644 --- a/crates/driver/src/infra/api/routes/settle/mod.rs +++ b/crates/driver/src/infra/api/routes/settle/mod.rs @@ -42,7 +42,9 @@ async fn route( // aborts the endpoint handler code. // This can happen due do connection issues or when the autopilot aborts // the `/settle` call when we reach the submission deadline. - Ok(tokio::task::spawn(handle_request) - .await - .unwrap_or_else(|_| Err(competition::Error::SubmissionError))?) + Ok( + ::observe::request_id::spawn_task_with_current_request_id(handle_request) + .await + .unwrap_or_else(|_| Err(competition::Error::SubmissionError))?, + ) } diff --git a/crates/observe/src/request_id.rs b/crates/observe/src/request_id.rs index 087010867e..268684409e 100644 --- a/crates/observe/src/request_id.rs +++ b/crates/observe/src/request_id.rs @@ -16,7 +16,8 @@ //! And when we issue requests to another process we can simply fetch the //! current identifier specific to our task and send that along with the //! request. -use std::future::Future; +use {std::future::Future, tokio::task::JoinHandle}; + tokio::task_local! { pub static REQUEST_ID: String; } @@ -39,6 +40,20 @@ where REQUEST_ID.scope(id, scope).await } +/// Spawns a new task and ensures it uses the same request id as the current +/// task (if present). This allows for tracing requests across task boundaries. +pub fn spawn_task_with_current_request_id(future: F) -> JoinHandle +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + if let Some(id) = get_task_local_storage() { + tokio::task::spawn(REQUEST_ID.scope(id, future)) + } else { + tokio::task::spawn(future) + } +} + /// Takes a `tower::Service` and embeds it in a `make_service` function that /// spawns one of these services per incoming request. /// But crucially before spawning that service task local storage will be From 5b26d737fd577c64d30121e83843720af492c39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Strug?= <47604705+mstrug@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:44:41 +0100 Subject: [PATCH 13/25] Storing call_data in quotes and order_quotes tables (#3124) # Description Added two new non-null columns: `metadata` (json) and `verified` (boolean) into tables `quotes` and `order_quotes`. Existing rows in database will be updated with `{}` and `false` values. `metadata` column is dedicated to store any additional data for the quote in json format. Currently it is used to store interactions received for `/quote` response from the solvers, but it is prepared to store other data in future through versioned `QuoteMetadata` struct as required. Database module only checks json validity, upper layers (orderbook, autopilot) uses `QuoteMetadata` struct for storing specific data in the database. Metadata can be used for verification/auditing of quotes/orders. Added database migration scripts (update and revert) and updated database readme file. ## How to test Existing tests, also added dedicated e2e tests and unit tests for this new functionality . --- Cargo.lock | 1 + crates/autopilot/src/database/auction.rs | 2 +- .../src/database/onchain_order_events/mod.rs | 4 + crates/database/Cargo.toml | 1 + crates/database/src/orders.rs | 70 ++++++++- crates/database/src/quotes.rs | 62 +++++++- crates/e2e/tests/e2e/database.rs | 11 ++ crates/e2e/tests/e2e/main.rs | 1 + .../e2e/tests/e2e/place_order_with_quote.rs | 107 ++++++++++++++ crates/e2e/tests/e2e/quote_verification.rs | 10 +- crates/orderbook/src/api/post_order.rs | 4 + crates/orderbook/src/database/orders.rs | 104 +++++++++++-- crates/orderbook/src/database/quotes.rs | 2 +- crates/orderbook/src/orderbook.rs | 15 +- crates/shared/src/event_storing_helpers.rs | 9 +- crates/shared/src/order_quoting.rs | 138 ++++++++++++++++++ .../src/price_estimation/competition/mod.rs | 10 +- crates/shared/src/price_estimation/mod.rs | 8 +- .../shared/src/price_estimation/native/mod.rs | 1 + .../shared/src/price_estimation/sanitized.rs | 14 ++ .../src/price_estimation/trade_finder.rs | 1 + .../src/price_estimation/trade_verifier.rs | 52 ++++--- crates/shared/src/trade_finding/external.rs | 5 + crates/shared/src/trade_finding/mod.rs | 33 ++++- database/README.md | 6 +- ...dd_metadata_to_quotes_and_order_quotes.sql | 11 ++ ...dd_metadata_to_quotes_and_order_quotes.sql | 29 ++++ 27 files changed, 653 insertions(+), 58 deletions(-) create mode 100644 crates/e2e/tests/e2e/place_order_with_quote.rs create mode 100644 database/sql/U075__add_metadata_to_quotes_and_order_quotes.sql create mode 100644 database/sql/V075__add_metadata_to_quotes_and_order_quotes.sql diff --git a/Cargo.lock b/Cargo.lock index 17ca562be0..90dc68d1b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1683,6 +1683,7 @@ dependencies = [ "futures", "hex", "maplit", + "serde_json", "sqlx", "strum", "tokio", diff --git a/crates/autopilot/src/database/auction.rs b/crates/autopilot/src/database/auction.rs index 30c4b037e4..606f0ce765 100644 --- a/crates/autopilot/src/database/auction.rs +++ b/crates/autopilot/src/database/auction.rs @@ -23,7 +23,7 @@ impl QuoteStoring for Postgres { .start_timer(); let mut ex = self.pool.acquire().await?; - let row = create_quote_row(data); + let row = create_quote_row(data)?; let id = database::quotes::save(&mut ex, &row).await?; Ok(id) } diff --git a/crates/autopilot/src/database/onchain_order_events/mod.rs b/crates/autopilot/src/database/onchain_order_events/mod.rs index 0c757d63ff..9ae6051493 100644 --- a/crates/autopilot/src/database/onchain_order_events/mod.rs +++ b/crates/autopilot/src/database/onchain_order_events/mod.rs @@ -487,6 +487,8 @@ async fn parse_general_onchain_order_placement_data<'a>( sell_amount: u256_to_big_decimal("e.sell_amount), buy_amount: u256_to_big_decimal("e.buy_amount), solver: ByteArray(quote.data.solver.0), + verified: quote.data.verified, + metadata: quote.data.metadata.try_into()?, }), Err(err) => { let err_label = err.to_metrics_label(); @@ -1187,6 +1189,8 @@ mod test { sell_amount: u256_to_big_decimal("e.sell_amount), buy_amount: u256_to_big_decimal("e.buy_amount), solver: ByteArray(quote.data.solver.0), + verified: quote.data.verified, + metadata: quote.data.metadata.try_into().unwrap(), }; assert_eq!(result.1, vec![Some(expected_quote)]); assert_eq!( diff --git a/crates/database/Cargo.toml b/crates/database/Cargo.toml index 572530a842..95a288e8df 100644 --- a/crates/database/Cargo.toml +++ b/crates/database/Cargo.toml @@ -13,6 +13,7 @@ futures = { workspace = true } hex = { workspace = true } sqlx = { workspace = true } strum = { workspace = true } +serde_json = { workspace = true } [dev-dependencies] maplit = { workspace = true } diff --git a/crates/database/src/orders.rs b/crates/database/src/orders.rs index a574dbc882..ebcc249abe 100644 --- a/crates/database/src/orders.rs +++ b/crates/database/src/orders.rs @@ -329,6 +329,8 @@ pub struct Quote { pub sell_amount: BigDecimal, pub buy_amount: BigDecimal, pub solver: Address, + pub verified: bool, + pub metadata: serde_json::Value, } pub async fn insert_quotes(ex: &mut PgConnection, quotes: &[Quote]) -> Result<(), sqlx::Error> { @@ -346,9 +348,11 @@ INSERT INTO order_quotes ( sell_token_price, sell_amount, buy_amount, - solver + solver, + verified, + metadata ) -VALUES ($1, $2, $3, $4, $5, $6, $7)"#; +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"#; pub async fn insert_quote_and_update_on_conflict( ex: &mut PgConnection, @@ -362,7 +366,7 @@ pub async fn insert_quote_and_update_on_conflict( " ON CONFLICT (order_uid) DO UPDATE SET gas_amount = $2, gas_price = $3, sell_token_price = $4, sell_amount = $5, -buy_amount = $6 +buy_amount = $6, verified = $8, metadata = $9 " ); sqlx::query(QUERY) @@ -373,6 +377,8 @@ buy_amount = $6 .bind("e.sell_amount) .bind("e.buy_amount) .bind(quote.solver) + .bind(quote.verified) + .bind("e.metadata) .execute(ex) .await?; Ok(()) @@ -387,6 +393,8 @@ pub async fn insert_quote(ex: &mut PgConnection, quote: &Quote) -> Result<(), sq .bind("e.sell_amount) .bind("e.buy_amount) .bind(quote.solver) + .bind(quote.verified) + .bind("e.metadata) .execute(ex) .await?; Ok(()) @@ -498,6 +506,8 @@ pub struct FullOrderWithQuote { pub quote_gas_amount: Option, pub quote_gas_price: Option, pub quote_sell_token_price: Option, + pub quote_verified: Option, + pub quote_metadata: Option, pub solver: Option
, } @@ -591,6 +601,8 @@ pub async fn single_full_order_with_quote( ", o_quotes.gas_amount as quote_gas_amount", ", o_quotes.gas_price as quote_gas_price", ", o_quotes.sell_token_price as quote_sell_token_price", + ", o_quotes.verified as quote_verified", + ", o_quotes.metadata as quote_metadata", ", o_quotes.solver as solver", " FROM ", FROM, " LEFT JOIN order_quotes o_quotes ON o.uid = o_quotes.order_uid", @@ -1201,6 +1213,8 @@ mod tests { sell_amount: 4.into(), buy_amount: 5.into(), solver: ByteArray([1; 20]), + verified: false, + metadata: Default::default(), }; insert_quote(&mut db, "e).await.unwrap(); insert_quote_and_update_on_conflict(&mut db, "e) @@ -1253,6 +1267,20 @@ mod tests { let mut db = db.begin().await.unwrap(); crate::clear_DANGER_(&mut db).await.unwrap(); + let metadata: serde_json::Value = serde_json::from_str( + r#"{ "version":"1.0", "interactions": [ { + "target": "0x0102030405060708091011121314151617181920", + "value": "1", + "callData": "0x0A0B0C102030" + },{ + "target": "0xFF02030405060708091011121314151617181920", + "value": "2", + "callData": "0xFF0B0C102030" + }] + }"#, + ) + .unwrap(); + let quote = Quote { order_uid: Default::default(), gas_amount: 1., @@ -1261,6 +1289,8 @@ mod tests { sell_amount: 4.into(), buy_amount: 5.into(), solver: ByteArray([1; 20]), + verified: true, + metadata, }; insert_quote(&mut db, "e).await.unwrap(); let quote_ = read_quote(&mut db, "e.order_uid) @@ -1287,6 +1317,8 @@ mod tests { sell_amount: 4.into(), buy_amount: 5.into(), solver: ByteArray([1; 20]), + verified: false, + metadata: Default::default(), }; insert_quote(&mut db, "e).await.unwrap(); let order_with_quote = single_full_order_with_quote(&mut db, "e.order_uid) @@ -2142,4 +2174,36 @@ mod tests { ] ); } + + #[tokio::test] + #[ignore] + async fn postgres_get_quote_with_no_metadata_and_validity() { + // This test checks backward compatibility + let mut db = PgConnection::connect("postgresql://").await.unwrap(); + let mut db = db.begin().await.unwrap(); + crate::clear_DANGER_(&mut db).await.unwrap(); + + let quote = Quote { + order_uid: Default::default(), + gas_amount: 1., + gas_price: 2., + sell_token_price: 3., + sell_amount: 4.into(), + buy_amount: 5.into(), + solver: ByteArray([1; 20]), + verified: false, + metadata: Default::default(), + }; + + // insert quote with verified and metadata fields stored as NULL + insert_quote_and_update_on_conflict(&mut db, "e) + .await + .unwrap(); + + let quote_ = read_quote(&mut db, "e.order_uid) + .await + .unwrap() + .unwrap(); + assert_eq!(quote, quote_); + } } diff --git a/crates/database/src/quotes.rs b/crates/database/src/quotes.rs index 41f94e50da..a932566c55 100644 --- a/crates/database/src/quotes.rs +++ b/crates/database/src/quotes.rs @@ -34,6 +34,8 @@ pub struct Quote { pub expiration_timestamp: DateTime, pub quote_kind: QuoteKind, pub solver: Address, + pub verified: bool, + pub metadata: serde_json::Value, } /// Stores the quote and returns the id. The id of the quote parameter is not @@ -51,9 +53,11 @@ INSERT INTO quotes ( order_kind, expiration_timestamp, quote_kind, - solver + solver, + verified, + metadata ) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id "#; let (id,) = sqlx::query_as(QUERY) @@ -68,6 +72,8 @@ RETURNING id .bind(quote.expiration_timestamp) .bind("e.quote_kind) .bind(quote.solver) + .bind(quote.verified) + .bind("e.metadata) .fetch_one(ex) .await?; Ok(id) @@ -181,6 +187,8 @@ mod tests { expiration_timestamp: now, quote_kind: QuoteKind::Standard, solver: ByteArray([1; 20]), + verified: false, + metadata: Default::default(), }; let id = save(&mut db, "e).await.unwrap(); quote.id = id; @@ -214,6 +222,8 @@ mod tests { expiration_timestamp: now, quote_kind: QuoteKind::Standard, solver: ByteArray([1; 20]), + verified: false, + metadata: Default::default(), }; let token_b = ByteArray([2; 20]); @@ -230,6 +240,8 @@ mod tests { expiration_timestamp: now, quote_kind: QuoteKind::Standard, solver: ByteArray([2; 20]), + verified: false, + metadata: Default::default(), }; // Save two measurements for token_a @@ -401,6 +413,8 @@ mod tests { expiration_timestamp: now, quote_kind: QuoteKind::Eip1271OnchainOrder, solver: ByteArray([1; 20]), + verified: false, + metadata: Default::default(), }; let id = save(&mut db, "e).await.unwrap(); quote.id = id; @@ -422,4 +436,48 @@ mod tests { search_a.quote_kind = QuoteKind::Standard; assert_eq!(find(&mut db, &search_a).await.unwrap(), None,); } + + #[tokio::test] + #[ignore] + async fn postgres_insert_quote_metadata() { + let mut db = PgConnection::connect("postgresql://").await.unwrap(); + let mut db = db.begin().await.unwrap(); + crate::clear_DANGER_(&mut db).await.unwrap(); + + let metadata: serde_json::Value = serde_json::from_str( + r#"{ "version":"1.0", "interactions": [ { + "target": "0x0102030405060708091011121314151617181920", + "value": "1", + "callData": "0x0A0B0C102030" + },{ + "target": "0xFF02030405060708091011121314151617181920", + "value": "2", + "callData": "0xFF0B0C102030" + }] + }"#, + ) + .unwrap(); + + let quote = Quote { + id: Default::default(), + sell_token: ByteArray([1; 20]), + buy_token: ByteArray([2; 20]), + sell_amount: 3.into(), + buy_amount: 4.into(), + gas_amount: 5., + gas_price: 6., + sell_token_price: 7., + order_kind: OrderKind::Sell, + expiration_timestamp: low_precision_now(), + quote_kind: QuoteKind::Standard, + solver: ByteArray([1; 20]), + verified: false, + metadata: metadata.clone(), + }; + // store quote in database + let id = save(&mut db, "e).await.unwrap(); + + let stored_quote = get(&mut db, id).await.unwrap().unwrap(); + assert_eq!(stored_quote.metadata, metadata); + } } diff --git a/crates/e2e/tests/e2e/database.rs b/crates/e2e/tests/e2e/database.rs index 382b74d685..5807361f05 100644 --- a/crates/e2e/tests/e2e/database.rs +++ b/crates/e2e/tests/e2e/database.rs @@ -19,6 +19,17 @@ pub async fn events_of_order(db: &Db, uid: &OrderUid) -> Vec Option<(serde_json::Value,)> { + const QUERY: &str = "SELECT metadata FROM quotes WHERE id = $1"; + let mut db = db.acquire().await.unwrap(); + sqlx::query_as(QUERY) + .bind(quote_id) + .fetch_optional(db.deref_mut()) + .await + .unwrap() +} + #[allow(dead_code)] #[derive(Clone, Debug, sqlx::FromRow)] pub struct AuctionTransaction { diff --git a/crates/e2e/tests/e2e/main.rs b/crates/e2e/tests/e2e/main.rs index 37534b4b70..e525829381 100644 --- a/crates/e2e/tests/e2e/main.rs +++ b/crates/e2e/tests/e2e/main.rs @@ -21,6 +21,7 @@ mod order_cancellation; mod partial_fill; mod partially_fillable_balance; mod partially_fillable_pool; +mod place_order_with_quote; mod protocol_fee; mod quote_verification; mod quoting; diff --git a/crates/e2e/tests/e2e/place_order_with_quote.rs b/crates/e2e/tests/e2e/place_order_with_quote.rs new file mode 100644 index 0000000000..09d3bb55d3 --- /dev/null +++ b/crates/e2e/tests/e2e/place_order_with_quote.rs @@ -0,0 +1,107 @@ +use { + driver::domain::eth::NonZeroU256, + e2e::{nodes::local_node::TestNodeApi, setup::*, tx, tx_value}, + ethcontract::U256, + model::{ + order::{OrderCreation, OrderKind}, + quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, + signature::EcdsaSigningScheme, + }, + secp256k1::SecretKey, + shared::ethrpc::Web3, + std::ops::DerefMut, + web3::signing::SecretKeyRef, +}; + +#[tokio::test] +#[ignore] +async fn local_node_test() { + run_test(place_order_with_quote).await; +} + +async fn place_order_with_quote(web3: Web3) { + let mut onchain = OnchainComponents::deploy(web3.clone()).await; + + let [solver] = onchain.make_solvers(to_wei(10)).await; + let [trader] = onchain.make_accounts(to_wei(10)).await; + let [token] = onchain + .deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000)) + .await; + + tx!( + trader.account(), + onchain + .contracts() + .weth + .approve(onchain.contracts().allowance, to_wei(3)) + ); + tx_value!( + trader.account(), + to_wei(3), + onchain.contracts().weth.deposit() + ); + + tracing::info!("Starting services."); + let services = Services::new(&onchain).await; + services.start_protocol(solver.clone()).await; + + // Disable auto-mine so we don't accidentally mine a settlement + web3.api::>() + .disable_automine() + .await + .expect("Must be able to disable automine"); + + tracing::info!("Quoting"); + let quote_sell_amount = to_wei(1); + let quote_request = OrderQuoteRequest { + from: trader.address(), + sell_token: onchain.contracts().weth.address(), + buy_token: token.address(), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::BeforeFee { + value: NonZeroU256::try_from(quote_sell_amount).unwrap(), + }, + }, + ..Default::default() + }; + let quote_response = services.submit_quote("e_request).await.unwrap(); + tracing::debug!(?quote_response); + assert!(quote_response.id.is_some()); + + let quote_metadata = + crate::database::quote_metadata(services.db(), quote_response.id.unwrap()).await; + assert!(quote_metadata.is_some()); + tracing::debug!(?quote_metadata); + + tracing::info!("Placing order"); + let balance = token.balance_of(trader.address()).call().await.unwrap(); + assert_eq!(balance, 0.into()); + let order = OrderCreation { + quote_id: quote_response.id, + sell_token: onchain.contracts().weth.address(), + sell_amount: quote_sell_amount, + buy_token: token.address(), + buy_amount: quote_response.quote.buy_amount, + valid_to: model::time::now_in_epoch_seconds() + 300, + kind: OrderKind::Sell, + ..Default::default() + } + .sign( + EcdsaSigningScheme::Eip712, + &onchain.contracts().domain_separator, + SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()), + ); + let order_uid = services.create_order(&order).await.unwrap(); + + tracing::info!("Order quote verification"); + let order_quote = database::orders::read_quote( + services.db().acquire().await.unwrap().deref_mut(), + &database::byte_array::ByteArray(order_uid.0), + ) + .await + .unwrap(); + assert!(order_quote.is_some()); + // compare quote metadata and order quote metadata + let order_quote_metadata = order_quote.unwrap().metadata; + assert_eq!(quote_metadata.unwrap().0, order_quote_metadata); +} diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 149afbfc7d..3b50d7ecc3 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -4,6 +4,7 @@ use { ethcontract::{H160, U256}, ethrpc::Web3, model::{ + interaction::InteractionData, order::{BuyTokenDestination, OrderKind, SellTokenSource}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, }, @@ -19,7 +20,7 @@ use { Estimate, Verification, }, - trade_finding::{Interaction, LegacyTrade, TradeKind}, + trade_finding::{Interaction, LegacyTrade, QuoteExecution, TradeKind}, }, std::{str::FromStr, sync::Arc}, }; @@ -175,6 +176,13 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { gas: 225000, solver: H160::from_str("0xe3067c7c27c1038de4e8ad95a83b927d23dfbd99").unwrap(), verified: true, + execution: QuoteExecution { + interactions: vec![InteractionData { + target: H160::from_str("0xdef1c0ded9bec7f1a1670819833240f027b25eff").unwrap(), + value: 0.into(), + call_data: hex::decode("aa77476c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000e357b42c3a9d8ccf0000000000000000000000000000000000000000000000000000000004d0e79e000000000000000000000000a69babef1ca67a37ffaf7a485dfff3382056e78c0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066360af101ffffffffffffffffffffffffffffffffffffff0f3f47f166360a8d0000003f0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001c66b3383f287dd9c85ad90e7c5a576ea4ba1bdf5a001d794a9afa379e6b2517b47e487a1aef32e75af432cbdbd301ada42754eaeac21ec4ca744afd92732f47540000000000000000000000000000000000000000000000000000000004d0c80f").unwrap() + }], + }, }; // `tx_origin: 0x0000` is currently used to bypass quote verification due to an diff --git a/crates/orderbook/src/api/post_order.rs b/crates/orderbook/src/api/post_order.rs index 2208bff6fd..e4d5bff45a 100644 --- a/crates/orderbook/src/api/post_order.rs +++ b/crates/orderbook/src/api/post_order.rs @@ -262,6 +262,10 @@ impl IntoWarpReply for AddOrderError { super::error("InvalidReplacement", err.to_string()), StatusCode::UNAUTHORIZED, ), + AddOrderError::MetadataSerializationFailed(err) => reply::with_status( + super::error("MetadataSerializationFailed", err.to_string()), + StatusCode::INTERNAL_SERVER_ERROR, + ), } } } diff --git a/crates/orderbook/src/database/orders.rs b/crates/orderbook/src/database/orders.rs index 2814664825..4a1d78fd30 100644 --- a/crates/orderbook/src/database/orders.rs +++ b/crates/orderbook/src/database/orders.rs @@ -1,5 +1,6 @@ use { super::Postgres, + crate::orderbook::AddOrderError, anyhow::{Context as _, Result}, app_data::AppDataHash, async_trait::async_trait, @@ -90,19 +91,29 @@ pub struct OrderWithQuote { } impl OrderWithQuote { - pub fn new(order: Order, quote: Option) -> Self { - Self { - quote: quote.map(|quote| orders::Quote { - order_uid: ByteArray(order.metadata.uid.0), - gas_amount: quote.data.fee_parameters.gas_amount, - gas_price: quote.data.fee_parameters.gas_price, - sell_token_price: quote.data.fee_parameters.sell_token_price, - sell_amount: u256_to_big_decimal("e.sell_amount), - buy_amount: u256_to_big_decimal("e.buy_amount), - solver: ByteArray(quote.data.solver.0), - }), + pub fn try_new(order: Order, quote: Option) -> Result { + Ok(Self { + quote: quote + .map(|quote| { + Ok::(orders::Quote { + order_uid: ByteArray(order.metadata.uid.0), + gas_amount: quote.data.fee_parameters.gas_amount, + gas_price: quote.data.fee_parameters.gas_price, + sell_token_price: quote.data.fee_parameters.sell_token_price, + sell_amount: u256_to_big_decimal("e.sell_amount), + buy_amount: u256_to_big_decimal("e.buy_amount), + solver: ByteArray(quote.data.solver.0), + verified: quote.data.verified, + metadata: quote + .data + .metadata + .try_into() + .map_err(AddOrderError::MetadataSerializationFailed)?, + }) + }) + .transpose()?, order, - } + }) } } @@ -112,6 +123,7 @@ pub enum InsertionError { DbError(sqlx::Error), /// Full app data to be inserted doesn't match existing. AppDataMismatch(Vec), + MetadataSerializationFailed(serde_json::Error), } impl From for InsertionError { @@ -233,11 +245,17 @@ async fn insert_quote( sell_amount: u256_to_big_decimal("e.sell_amount), buy_amount: u256_to_big_decimal("e.buy_amount), solver: ByteArray(quote.data.solver.0), + verified: quote.data.verified, + metadata: quote + .data + .metadata + .clone() + .try_into() + .map_err(InsertionError::MetadataSerializationFailed)?, }; database::orders::insert_quote(ex, "e) .await - .map_err(InsertionError::DbError)?; - Ok(()) + .map_err(InsertionError::DbError) } #[async_trait::async_trait] @@ -362,6 +380,8 @@ impl OrderStoring for Postgres { order_with_quote.quote_gas_amount, order_with_quote.quote_gas_price, order_with_quote.quote_sell_token_price, + order_with_quote.quote_verified, + order_with_quote.quote_metadata, order_with_quote.solver, ) { ( @@ -370,6 +390,8 @@ impl OrderStoring for Postgres { Some(gas_amount), Some(gas_price), Some(sell_token_price), + Some(verified), + Some(metadata), Some(solver), ) => Some(orders::Quote { order_uid: order_with_quote.full_order.uid, @@ -379,6 +401,8 @@ impl OrderStoring for Postgres { sell_amount, buy_amount, solver, + verified, + metadata, }), _ => None, }; @@ -668,6 +692,7 @@ mod tests { signature::{Signature, SigningScheme}, }, primitive_types::U256, + shared::order_quoting::{QuoteData, QuoteMetadataV1}, std::sync::atomic::{AtomicI64, Ordering}, }; @@ -1170,4 +1195,55 @@ mod tests { u256_to_big_decimal("e.buy_amount) ); } + + #[tokio::test] + #[ignore] + async fn postgres_insert_orders_with_interactions_and_verified() { + let db = Postgres::new("postgresql://").unwrap(); + database::clear_DANGER(&db.pool).await.unwrap(); + + let uid = OrderUid([0x42; 56]); + let order = Order { + data: OrderData { + valid_to: u32::MAX, + ..Default::default() + }, + metadata: OrderMetadata { + uid, + ..Default::default() + }, + ..Default::default() + }; + + let quote = Quote { + id: Some(5), + sell_amount: U256::from(1), + buy_amount: U256::from(2), + data: QuoteData { + verified: true, + metadata: QuoteMetadataV1 { + interactions: vec![ + InteractionData { + target: H160([1; 20]), + value: U256::from(100), + call_data: vec![1, 20], + }, + InteractionData { + target: H160([2; 20]), + value: U256::from(10), + call_data: vec![2, 20], + }, + ], + } + .into(), + ..Default::default() + }, + ..Default::default() + }; + db.insert_order(&order, Some(quote)).await.unwrap(); + + let single_order_with_quote = db.single_order_with_quote(&uid).await.unwrap().unwrap(); + assert_eq!(single_order_with_quote.order, order); + assert!(single_order_with_quote.quote.unwrap().verified); + } } diff --git a/crates/orderbook/src/database/quotes.rs b/crates/orderbook/src/database/quotes.rs index 0cb149cc86..8e2608e0c1 100644 --- a/crates/orderbook/src/database/quotes.rs +++ b/crates/orderbook/src/database/quotes.rs @@ -18,7 +18,7 @@ impl QuoteStoring for Postgres { .start_timer(); let mut ex = self.pool.acquire().await?; - let row = create_quote_row(data); + let row = create_quote_row(data)?; let id = database::quotes::save(&mut ex, &row).await?; Ok(id) } diff --git a/crates/orderbook/src/orderbook.rs b/crates/orderbook/src/orderbook.rs index fc637a1c2f..2b4a6251c4 100644 --- a/crates/orderbook/src/orderbook.rs +++ b/crates/orderbook/src/orderbook.rs @@ -141,6 +141,8 @@ pub enum AddOrderError { provided: String, existing: String, }, + #[error("quote metadata failed to serialize as json, error: {0}")] + MetadataSerializationFailed(serde_json::Error), } impl AddOrderError { @@ -161,6 +163,9 @@ impl AddOrderError { s.into_owned() }, }, + InsertionError::MetadataSerializationFailed(err) => { + AddOrderError::MetadataSerializationFailed(err) + } } } } @@ -249,17 +254,18 @@ impl Orderbook { self.replace_order(order, old_order, quote).await } else { let quote_id = quote.as_ref().and_then(|quote| quote.id); + let order_uid = order.metadata.uid; self.database .insert_order(&order, quote.clone()) .await .map_err(|err| AddOrderError::from_insertion(err, &order))?; Metrics::on_order_operation( - &OrderWithQuote::new(order.clone(), quote), + &OrderWithQuote::try_new(order, quote)?, OrderOperation::Created, ); - Ok((order.metadata.uid, quote_id)) + Ok((order_uid, quote_id)) } } @@ -402,6 +408,7 @@ impl Orderbook { } let quote_id = quote.as_ref().and_then(|quote| quote.id); + let order_uid = validated_new_order.metadata.uid; self.database .replace_order( @@ -413,11 +420,11 @@ impl Orderbook { .map_err(|err| AddOrderError::from_insertion(err, &validated_new_order))?; Metrics::on_order_operation(&old_order, OrderOperation::Cancelled); Metrics::on_order_operation( - &OrderWithQuote::new(validated_new_order.clone(), quote), + &OrderWithQuote::try_new(validated_new_order, quote)?, OrderOperation::Created, ); - Ok((validated_new_order.metadata.uid, quote_id)) + Ok((order_uid, quote_id)) } pub async fn get_order(&self, uid: &OrderUid) -> Result> { diff --git a/crates/shared/src/event_storing_helpers.rs b/crates/shared/src/event_storing_helpers.rs index 1f1938b2c6..e3684df76f 100644 --- a/crates/shared/src/event_storing_helpers.rs +++ b/crates/shared/src/event_storing_helpers.rs @@ -3,6 +3,7 @@ use { db_order_conversions::order_kind_into, order_quoting::{quote_kind_from_signing_scheme, QuoteData, QuoteSearchParameters}, }, + anyhow::Result, chrono::{DateTime, Utc}, database::{ byte_array::ByteArray, @@ -11,8 +12,8 @@ use { number::conversions::u256_to_big_decimal, }; -pub fn create_quote_row(data: QuoteData) -> DbQuote { - DbQuote { +pub fn create_quote_row(data: QuoteData) -> Result { + Ok(DbQuote { id: Default::default(), sell_token: ByteArray(data.sell_token.0), buy_token: ByteArray(data.buy_token.0), @@ -25,7 +26,9 @@ pub fn create_quote_row(data: QuoteData) -> DbQuote { expiration_timestamp: data.expiration, quote_kind: data.quote_kind, solver: ByteArray(data.solver.0), - } + verified: data.verified, + metadata: data.metadata.try_into()?, + }) } pub fn create_db_search_parameters( diff --git a/crates/shared/src/order_quoting.rs b/crates/shared/src/order_quoting.rs index eac107f379..6942655449 100644 --- a/crates/shared/src/order_quoting.rs +++ b/crates/shared/src/order_quoting.rs @@ -170,6 +170,8 @@ pub struct QuoteData { pub solver: H160, /// Were we able to verify that this quote is accurate? pub verified: bool, + /// Additional data associated with the quote. + pub metadata: QuoteMetadata, } impl TryFrom for QuoteData { @@ -195,6 +197,7 @@ impl TryFrom for QuoteData { // Even if the quote was verified at the time of creation // it might no longer be accurate. verified: false, + metadata: row.metadata.try_into()?, }) } } @@ -442,6 +445,10 @@ impl OrderQuoter { quote_kind, solver: trade_estimate.solver, verified: trade_estimate.verified, + metadata: QuoteMetadataV1 { + interactions: trade_estimate.execution.interactions, + } + .into(), }; Ok(quote) @@ -629,6 +636,62 @@ pub fn quote_kind_from_signing_scheme(scheme: &QuoteSigningScheme) -> QuoteKind } } +/// Used to store quote metadata in the database. +/// Versioning is used for the backward compatibility. +/// In case new metadata needs to be associated with a quote create a new +/// variant version and apply serde rename attribute with proper number. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase", tag = "version")] +pub enum QuoteMetadata { + #[serde(rename = "1.0")] + V1(QuoteMetadataV1), +} + +// Handles deserialization of empty json value {} in metadata column. +#[derive(Clone, Debug, PartialEq, serde::Deserialize)] +#[serde(untagged)] +enum QuoteMetadataDeserializationHelper { + Data(QuoteMetadata), + Empty {}, +} + +impl TryInto for QuoteMetadata { + type Error = serde_json::Error; + + fn try_into(self) -> std::result::Result { + serde_json::to_value(self) + } +} + +impl TryFrom for QuoteMetadata { + type Error = serde_json::Error; + + fn try_from(value: serde_json::Value) -> std::result::Result { + Ok(match serde_json::from_value(value)? { + QuoteMetadataDeserializationHelper::Data(value) => value, + QuoteMetadataDeserializationHelper::Empty {} => Default::default(), + }) + } +} + +impl Default for QuoteMetadata { + fn default() -> Self { + Self::V1(Default::default()) + } +} + +impl From for QuoteMetadata { + fn from(val: QuoteMetadataV1) -> Self { + QuoteMetadata::V1(val) + } +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct QuoteMetadataV1 { + /// Data provided by the solver in response to /quote request. + pub interactions: Vec, +} + #[cfg(test)] mod tests { use { @@ -727,6 +790,7 @@ mod tests { gas: 3, solver: H160([1; 20]), verified: false, + execution: Default::default(), }) } .boxed() @@ -768,6 +832,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), })) .returning(|_| Ok(1337)); @@ -804,6 +869,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), }, sell_amount: 70.into(), buy_amount: 29.into(), @@ -862,6 +928,7 @@ mod tests { gas: 3, solver: H160([1; 20]), verified: false, + execution: Default::default(), }) } .boxed() @@ -903,6 +970,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), })) .returning(|_| Ok(1337)); @@ -939,6 +1007,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), }, sell_amount: 100.into(), buy_amount: 42.into(), @@ -992,6 +1061,7 @@ mod tests { gas: 3, solver: H160([1; 20]), verified: false, + execution: Default::default(), }) } .boxed() @@ -1033,6 +1103,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), })) .returning(|_| Ok(1337)); @@ -1069,6 +1140,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), }, sell_amount: 100.into(), buy_amount: 42.into(), @@ -1108,6 +1180,7 @@ mod tests { gas: 200, solver: H160([1; 20]), verified: false, + execution: Default::default(), }) } .boxed() @@ -1179,6 +1252,7 @@ mod tests { gas: 200, solver: H160([1; 20]), verified: false, + execution: Default::default(), }) } .boxed() @@ -1255,6 +1329,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), })) }); @@ -1288,6 +1363,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), }, sell_amount: 85.into(), // Allows for "out-of-price" buy amounts. This means that order @@ -1335,6 +1411,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), })) }); @@ -1368,6 +1445,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), }, sell_amount: 100.into(), buy_amount: 42.into(), @@ -1416,6 +1494,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), }, ))) }); @@ -1450,6 +1529,7 @@ mod tests { quote_kind: QuoteKind::Standard, solver: H160([1; 20]), verified: false, + metadata: Default::default(), }, sell_amount: 100.into(), buy_amount: 42.into(), @@ -1547,4 +1627,62 @@ mod tests { FindQuoteError::NotFound(None), )); } + + #[test] + fn check_quote_metadata_format() { + let q: QuoteMetadata = QuoteMetadataV1 { + interactions: vec![ + InteractionData { + target: H160::from([1; 20]), + value: U256::one(), + call_data: vec![1], + }, + InteractionData { + target: H160::from([2; 20]), + value: U256::from(2), + call_data: vec![2], + }, + ], + } + .into(); + let v = serde_json::to_value(q).unwrap(); + + let req: serde_json::Value = serde_json::from_str( + r#" + {"version":"1.0", + "interactions":[ + {"target":"0x0101010101010101010101010101010101010101","value":"1","callData":"0x01"}, + {"target":"0x0202020202020202020202020202020202020202","value":"2","callData":"0x02"} + ]}"#, + ) + .unwrap(); + + assert_eq!(req, v); + } + + #[test] + fn check_quote_metadata_deserialize_from_empty_json() { + let empty_json: serde_json::Value = serde_json::from_str("{}").unwrap(); + let metadata: QuoteMetadata = empty_json.try_into().unwrap(); + // Empty json is converted to QuoteMetadata default value + assert_eq!(metadata, QuoteMetadata::default()); + } + + #[test] + fn check_quote_metadata_deserialize_from_v1_json() { + let v1: serde_json::Value = serde_json::from_str( + r#" + {"version":"1.0", + "interactions":[ + {"target":"0x0101010101010101010101010101010101010101","value":"1","callData":"0x01"}, + {"target":"0x0202020202020202020202020202020202020202","value":"2","callData":"0x02"} + ]}"#, + ) + .unwrap(); + let metadata: QuoteMetadata = v1.try_into().unwrap(); + + match metadata { + QuoteMetadata::V1(v1) => assert_eq!(v1.interactions.len(), 2), + } + } } diff --git a/crates/shared/src/price_estimation/competition/mod.rs b/crates/shared/src/price_estimation/competition/mod.rs index 2703e3397a..3800f53ddf 100644 --- a/crates/shared/src/price_estimation/competition/mod.rs +++ b/crates/shared/src/price_estimation/competition/mod.rs @@ -287,17 +287,17 @@ mod tests { }; let first = setup_estimator(vec![ - Ok(estimates[0]), - Ok(estimates[0]), - Ok(estimates[0]), + Ok(estimates[0].clone()), + Ok(estimates[0].clone()), + Ok(estimates[0].clone()), Err(PriceEstimationError::ProtocolInternal(anyhow!("a"))), Err(PriceEstimationError::NoLiquidity), ]); let second = setup_estimator(vec![ Err(PriceEstimationError::ProtocolInternal(anyhow!(""))), - Ok(estimates[1]), - Ok(estimates[1]), + Ok(estimates[1].clone()), + Ok(estimates[1].clone()), Err(PriceEstimationError::ProtocolInternal(anyhow!("b"))), Err(PriceEstimationError::UnsupportedToken { token: H160([0; 20]), diff --git a/crates/shared/src/price_estimation/mod.rs b/crates/shared/src/price_estimation/mod.rs index 5db9810d01..c077f83906 100644 --- a/crates/shared/src/price_estimation/mod.rs +++ b/crates/shared/src/price_estimation/mod.rs @@ -2,7 +2,7 @@ use { self::trade_verifier::balance_overrides, crate::{ arguments::{display_option, display_secret_option, ExternalSolver}, - trade_finding::Interaction, + trade_finding::{Interaction, QuoteExecution}, }, anyhow::Result, bigdecimal::BigDecimal, @@ -461,7 +461,7 @@ pub struct Verification { pub buy_token_destination: BuyTokenDestination, } -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] pub struct Estimate { pub out_amount: U256, /// full gas cost when settling this order alone on gp @@ -470,6 +470,8 @@ pub struct Estimate { pub solver: H160, /// Did we verify the correctness of this estimate's properties? pub verified: bool, + /// Data associated with this estimation. + pub execution: QuoteExecution, } impl Estimate { @@ -530,7 +532,7 @@ pub mod mocks { pub struct FakePriceEstimator(pub Estimate); impl PriceEstimating for FakePriceEstimator { fn estimate(&self, _query: Arc) -> BoxFuture<'_, PriceEstimateResult> { - async { Ok(self.0) }.boxed() + async { Ok(self.0.clone()) }.boxed() } } diff --git a/crates/shared/src/price_estimation/native/mod.rs b/crates/shared/src/price_estimation/native/mod.rs index e0928ddb8d..31ca840575 100644 --- a/crates/shared/src/price_estimation/native/mod.rs +++ b/crates/shared/src/price_estimation/native/mod.rs @@ -130,6 +130,7 @@ mod tests { gas: 0, solver: H160([1; 20]), verified: false, + execution: Default::default(), }) } .boxed() diff --git a/crates/shared/src/price_estimation/sanitized.rs b/crates/shared/src/price_estimation/sanitized.rs index 29c9f44aff..926703a4b8 100644 --- a/crates/shared/src/price_estimation/sanitized.rs +++ b/crates/shared/src/price_estimation/sanitized.rs @@ -67,6 +67,7 @@ impl PriceEstimating for SanitizedPriceEstimator { gas: 0, solver: Default::default(), verified: true, + execution: Default::default(), }; tracing::debug!(?query, ?estimation, "generate trivial price estimation"); return Ok(estimation); @@ -79,6 +80,7 @@ impl PriceEstimating for SanitizedPriceEstimator { gas: GAS_PER_WETH_UNWRAP, solver: Default::default(), verified: true, + execution: Default::default(), }; tracing::debug!(?query, ?estimation, "generate trivial unwrap estimation"); return Ok(estimation); @@ -91,6 +93,7 @@ impl PriceEstimating for SanitizedPriceEstimator { gas: GAS_PER_WETH_WRAP, solver: Default::default(), verified: true, + execution: Default::default(), }; tracing::debug!(?query, ?estimation, "generate trivial wrap estimation"); return Ok(estimation); @@ -185,6 +188,7 @@ mod tests { gas: 100, solver: Default::default(), verified: false, + execution: Default::default(), }), ), // `sanitized_estimator` will replace `buy_token` with `native_token` before querying @@ -206,6 +210,7 @@ mod tests { gas: GAS_PER_WETH_UNWRAP + 100, solver: Default::default(), verified: false, + execution: Default::default(), }), ), // Will cause buffer overflow of gas price in `sanitized_estimator`. @@ -241,6 +246,7 @@ mod tests { gas: GAS_PER_WETH_WRAP + 100, solver: Default::default(), verified: false, + execution: Default::default(), }), ), // Can be estimated by `sanitized_estimator` because `buy_token` and `sell_token` are @@ -259,6 +265,7 @@ mod tests { gas: 0, solver: Default::default(), verified: true, + execution: Default::default(), }), ), // Can be estimated by `sanitized_estimator` because both tokens are the native token. @@ -276,6 +283,7 @@ mod tests { gas: 0, solver: Default::default(), verified: true, + execution: Default::default(), }), ), // Can be estimated by `sanitized_estimator` because it is a native token unwrap. @@ -294,6 +302,7 @@ mod tests { gas: GAS_PER_WETH_UNWRAP, solver: Default::default(), verified: true, + execution: Default::default(), }), ), // Can be estimated by `sanitized_estimator` because it is a native token wrap. @@ -312,6 +321,7 @@ mod tests { gas: GAS_PER_WETH_WRAP, solver: Default::default(), verified: true, + execution: Default::default(), }), ), // Will throw `UnsupportedToken` error in `sanitized_estimator`. @@ -376,6 +386,7 @@ mod tests { gas: 100, solver: Default::default(), verified: false, + execution: Default::default(), }) } .boxed() @@ -391,6 +402,7 @@ mod tests { gas: 100, solver: Default::default(), verified: false, + execution: Default::default(), }) } .boxed() @@ -406,6 +418,7 @@ mod tests { gas: u64::MAX, solver: Default::default(), verified: false, + execution: Default::default(), }) } .boxed() @@ -421,6 +434,7 @@ mod tests { gas: 100, solver: Default::default(), verified: false, + execution: Default::default(), }) } .boxed() diff --git a/crates/shared/src/price_estimation/trade_finder.rs b/crates/shared/src/price_estimation/trade_finder.rs index 8bbe471f36..dcfd7a8d30 100644 --- a/crates/shared/src/price_estimation/trade_finder.rs +++ b/crates/shared/src/price_estimation/trade_finder.rs @@ -89,6 +89,7 @@ impl Inner { gas: quote.gas_estimate, solver: quote.solver, verified: false, + execution: quote.execution, }) } } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 496cecdd4f..7098695c91 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -9,8 +9,10 @@ use { encoded_settlement::{encode_trade, EncodedSettlement, EncodedTrade}, interaction::EncodedInteraction, trade_finding::{ - external::{dto, dto::JitOrder}, + external::dto::{self, JitOrder}, + map_interactions_data, Interaction, + QuoteExecution, TradeKind, }, }, @@ -216,6 +218,9 @@ impl TradeVerifier { gas: trade.gas_estimate().context("no gas estimate")?, solver: trade.solver(), verified: true, + execution: QuoteExecution { + interactions: map_interactions_data(&trade.interactions()), + }, }; tracing::warn!( ?estimate, @@ -272,12 +277,7 @@ impl TradeVerifier { "verified quote", ); - ensure_quote_accuracy( - &self.quote_inaccuracy_limit, - query, - trade.solver(), - &summary, - ) + ensure_quote_accuracy(&self.quote_inaccuracy_limit, query, trade, &summary) } /// Configures all the state overrides that are needed to mock the given @@ -429,6 +429,9 @@ impl TradeVerifying for TradeVerifier { gas, solver: trade.solver(), verified: false, + execution: QuoteExecution { + interactions: map_interactions_data(&trade.interactions()), + }, }; tracing::warn!( ?err, @@ -742,7 +745,7 @@ impl SettleOutput { fn ensure_quote_accuracy( inaccuracy_limit: &BigRational, query: &PriceQuery, - solver: H160, + trade: &TradeKind, summary: &SettleOutput, ) -> std::result::Result { // amounts verified by the simulation @@ -773,8 +776,11 @@ fn ensure_quote_accuracy( Ok(Estimate { out_amount: summary.out_amount, gas: summary.gas_used.as_u64(), - solver, + solver: trade.solver(), verified: true, + execution: QuoteExecution { + interactions: map_interactions_data(&trade.interactions()), + }, }) } @@ -828,7 +834,8 @@ mod tests { out_amount: 2_000.into(), tokens_lost, }; - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + let estimate = + ensure_quote_accuracy(&low_threshold, &query, &TradeKind::default(), &summary); assert!(matches!(estimate, Err(Error::SimulationFailed(_)))); // sell token is lost @@ -840,7 +847,9 @@ mod tests { out_amount: 2_000.into(), tokens_lost, }; - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + + let estimate = + ensure_quote_accuracy(&low_threshold, &query, &TradeKind::default(), &summary); assert!(matches!(estimate, Err(Error::SimulationFailed(_)))); // everything is in-place @@ -853,7 +862,8 @@ mod tests { out_amount: 2_000.into(), tokens_lost, }; - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + let estimate = + ensure_quote_accuracy(&low_threshold, &query, &TradeKind::default(), &summary); assert!(estimate.is_ok()); let tokens_lost = hashmap! { @@ -867,11 +877,13 @@ mod tests { tokens_lost, }; - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &sell_more); + let estimate = + ensure_quote_accuracy(&low_threshold, &query, &Default::default(), &sell_more); assert!(matches!(estimate, Err(Error::TooInaccurate))); // passes with slightly higher tolerance - let estimate = ensure_quote_accuracy(&high_threshold, &query, H160::zero(), &sell_more); + let estimate = + ensure_quote_accuracy(&high_threshold, &query, &Default::default(), &sell_more); assert!(estimate.is_ok()); let tokens_lost = hashmap! { @@ -885,11 +897,13 @@ mod tests { tokens_lost, }; - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &pay_out_more); + let estimate = + ensure_quote_accuracy(&low_threshold, &query, &Default::default(), &pay_out_more); assert!(matches!(estimate, Err(Error::TooInaccurate))); // passes with slightly higher tolerance - let estimate = ensure_quote_accuracy(&high_threshold, &query, H160::zero(), &pay_out_more); + let estimate = + ensure_quote_accuracy(&high_threshold, &query, &Default::default(), &pay_out_more); assert!(estimate.is_ok()); let tokens_lost = hashmap! { @@ -903,7 +917,8 @@ mod tests { tokens_lost, }; // Ending up with surplus in the buffers is always fine - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &sell_less); + let estimate = + ensure_quote_accuracy(&low_threshold, &query, &Default::default(), &sell_less); assert!(estimate.is_ok()); let tokens_lost = hashmap! { @@ -917,7 +932,8 @@ mod tests { tokens_lost, }; // Ending up with surplus in the buffers is always fine - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &pay_out_less); + let estimate = + ensure_quote_accuracy(&low_threshold, &query, &Default::default(), &pay_out_less); assert!(estimate.is_ok()); } } diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 8aef3be293..95e820eb70 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -5,9 +5,11 @@ use { price_estimation::{PriceEstimationError, Query}, request_sharing::{BoxRequestSharing, RequestSharing}, trade_finding::{ + map_interactions_data, Interaction, LegacyTrade, Quote, + QuoteExecution, Trade, TradeError, TradeFinding, @@ -220,6 +222,9 @@ impl TradeFinding for ExternalTradeFinder { .map_err(TradeError::Other)?, gas_estimate, solver: trade.solver(), + execution: QuoteExecution { + interactions: map_interactions_data(&trade.interactions()), + }, }) } diff --git a/crates/shared/src/trade_finding/mod.rs b/crates/shared/src/trade_finding/mod.rs index 4f0fbbacf0..0c0cc9a5ce 100644 --- a/crates/shared/src/trade_finding/mod.rs +++ b/crates/shared/src/trade_finding/mod.rs @@ -15,7 +15,7 @@ use { model::{interaction::InteractionData, order::OrderKind}, num::CheckedDiv, number::conversions::big_rational_to_u256, - serde::Serialize, + serde::{Deserialize, Serialize}, std::{collections::HashMap, ops::Mul}, thiserror::Error, }; @@ -36,6 +36,13 @@ pub struct Quote { pub out_amount: U256, pub gas_estimate: u64, pub solver: H160, + pub execution: QuoteExecution, +} + +/// Quote execution metadata. +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] +pub struct QuoteExecution { + pub interactions: Vec, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -44,6 +51,12 @@ pub enum TradeKind { Regular(Trade), } +impl Default for TradeKind { + fn default() -> Self { + Self::Legacy(LegacyTrade::default()) + } +} + impl TradeKind { pub fn gas_estimate(&self) -> Option { match self { @@ -181,6 +194,14 @@ impl Interaction { pub fn encode(&self) -> EncodedInteraction { (self.target, self.value, Bytes(self.data.clone())) } + + pub fn to_interaction_data(&self) -> InteractionData { + InteractionData { + target: self.target, + value: self.value, + call_data: self.data.clone(), + } + } } impl From for Interaction { @@ -248,6 +269,14 @@ pub fn map_interactions(interactions: &[InteractionData]) -> Vec { interactions.iter().cloned().map(Into::into).collect() } +pub fn map_interactions_data(interactions: &[Interaction]) -> Vec { + interactions + .iter() + .cloned() + .map(|i| i.to_interaction_data()) + .collect() +} + #[cfg(test)] mod tests { use super::*; @@ -266,6 +295,6 @@ mod tests { interaction_debug, "Interaction { target: 0x0000000000000000000000000000000000000000, value: 1, data: \ 0x010203040506 }" - ); + ) } } diff --git a/database/README.md b/database/README.md index 0a9e8a0bcb..abb2c53b29 100644 --- a/database/README.md +++ b/database/README.md @@ -229,7 +229,7 @@ Indexes: Quotes that an order was created with. These quotes get stored persistently and can be used to evaluate how accurate the quoted fee predicted the execution cost that actually happened on-chain. - Colmun | Type | Nullable | Details + Column | Type | Nullable | Details --------------------|---------|----------|-------- order\_uid | bytea | not null | order that this quote belongs to gas\_amount | double | not null | estimated gas used by the quote used to create this order with @@ -238,6 +238,8 @@ Quotes that an order was created with. These quotes get stored persistently and sell\_amount | numeric | not null | sell\_amount of the quote used to create the order with buy\_amount | numeric | not null | buy\_amount of the quote used to create the order with solver | bytea | not null | public address of the solver that provided this quote + verified | boolean | not null | information if quote was verified + metadata | json | not null | additional data associated with the quote in json format Indexes: - PRIMARY KEY: btree(`order_uid`) @@ -339,6 +341,8 @@ Stores quotes in order to determine whether it makes sense to allow a user to cr id | bigint | not null | unique identifier of this quote quote\_kind | [enum](#quotekind) | not null | quotekind for which this quote is considered valid solver | bytea | not null | public address of the solver that provided this quote + verified | boolean | not null | information if quote was verified + metadata | json | not null | additional data associated with the quote in json format Indexes: - PRIMARY KEY: btree(`id`) diff --git a/database/sql/U075__add_metadata_to_quotes_and_order_quotes.sql b/database/sql/U075__add_metadata_to_quotes_and_order_quotes.sql new file mode 100644 index 0000000000..e0f15152ff --- /dev/null +++ b/database/sql/U075__add_metadata_to_quotes_and_order_quotes.sql @@ -0,0 +1,11 @@ +-- This script reverts changes applied in V075__add_metadata_to_quotes_and_order_quotes.sql migration script. + +-- Step 1: Drop two columns from the quotes table +ALTER TABLE quotes + DROP COLUMN verified, + DROP COLUMN metadata; + +-- Step 2: Drop two columns from the order_quotes table +ALTER TABLE order_quotes + DROP COLUMN verified, + DROP COLUMN metadata; diff --git a/database/sql/V075__add_metadata_to_quotes_and_order_quotes.sql b/database/sql/V075__add_metadata_to_quotes_and_order_quotes.sql new file mode 100644 index 0000000000..363c098f90 --- /dev/null +++ b/database/sql/V075__add_metadata_to_quotes_and_order_quotes.sql @@ -0,0 +1,29 @@ +-- This migration script is not reversible. + +-- Step 1: Add two new columns to the quotes table +ALTER TABLE quotes + ADD COLUMN verified boolean, + ADD COLUMN metadata json; + +-- Step 2: Update existing data with non-null values +UPDATE quotes SET verified = false, metadata = '{}'::json; + +-- Step 3: Add NOT NULL constraint to newly added columns +ALTER TABLE quotes + ALTER COLUMN verified SET NOT NULL, + ALTER COLUMN metadata SET NOT NULL; + + +-- Step 4: Add two new columns to the order_quotes table +ALTER TABLE order_quotes + ADD COLUMN verified boolean, + ADD COLUMN metadata json; + +-- Step 5: Update existing data with non-null values +UPDATE order_quotes SET verified = false, metadata = '{}'::json; + +-- Step 6: Add NOT NULL constraint to newly added columns +ALTER TABLE order_quotes + ALTER COLUMN verified SET NOT NULL, + ALTER COLUMN metadata SET NOT NULL; + From df9ca2b195f18da80b654369f8d404dc5acb254c Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Thu, 12 Dec 2024 12:27:18 +0100 Subject: [PATCH 14/25] Remove naive solver (#3161) # Description The naive solver is extremely basic and never wins any auctions anymore. Also AFAICT it's no longer used in e2e tests either so by now it's just dead weight. Sparked by this [thread](https://cowservices.slack.com/archives/C036JAGRQ04/p1733911881085109) # Changes Removes: * original algorithm in legacy code * wrapper logic in `solvers` crate * config code * test cases * documentation Also in many cases we had an enum with 2 variants (`baseline` and `naive`). Since we now only have 1 variant left I collapsed all the logic and got rid of a few modules which are no longer necessary. --- README.md | 11 - crates/solver/src/settlement/mod.rs | 8 - .../{solver/baseline_solver.rs => solver.rs} | 20 + crates/solver/src/solver/mod.rs | 25 - crates/solver/src/solver/naive_solver.rs | 1086 ----------------- crates/solvers/src/boundary/baseline.rs | 14 +- crates/solvers/src/boundary/mod.rs | 1 - crates/solvers/src/boundary/naive.rs | 249 ---- .../domain/{solver/baseline.rs => solver.rs} | 12 +- crates/solvers/src/domain/solver/mod.rs | 30 - crates/solvers/src/domain/solver/naive.rs | 107 -- crates/solvers/src/infra/cli.rs | 2 - .../infra/{config/baseline.rs => config.rs} | 28 +- crates/solvers/src/infra/config/mod.rs | 21 - crates/solvers/src/run.rs | 7 +- .../{baseline => cases}/bal_liquidity.rs | 0 .../{baseline => cases}/buy_order_rounding.rs | 0 .../tests/{baseline => cases}/direct_swap.rs | 0 .../{baseline => cases}/internalization.rs | 0 .../limit_order_quoting.rs | 0 .../src/tests/{baseline => cases}/mod.rs | 0 .../tests/{baseline => cases}/partial_fill.rs | 0 crates/solvers/src/tests/mod.rs | 3 +- .../src/tests/naive/extract_deepest_pool.rs | 139 --- .../naive/filters_out_of_price_orders.rs | 172 --- .../src/tests/naive/limit_order_price.rs | 70 -- .../solvers/src/tests/naive/matches_orders.rs | 508 -------- crates/solvers/src/tests/naive/mod.rs | 8 - .../src/tests/naive/reserves_too_small.rs | 123 -- .../rounds_prices_in_favour_of_traders.rs | 137 --- .../tests/naive/swap_less_than_reserves.rs | 99 -- .../solvers/src/tests/naive/without_pool.rs | 120 -- 32 files changed, 60 insertions(+), 2940 deletions(-) rename crates/solver/src/{solver/baseline_solver.rs => solver.rs} (73%) delete mode 100644 crates/solver/src/solver/mod.rs delete mode 100644 crates/solver/src/solver/naive_solver.rs delete mode 100644 crates/solvers/src/boundary/naive.rs rename crates/solvers/src/domain/{solver/baseline.rs => solver.rs} (97%) delete mode 100644 crates/solvers/src/domain/solver/mod.rs delete mode 100644 crates/solvers/src/domain/solver/naive.rs rename crates/solvers/src/infra/{config/baseline.rs => config.rs} (81%) delete mode 100644 crates/solvers/src/infra/config/mod.rs rename crates/solvers/src/tests/{baseline => cases}/bal_liquidity.rs (100%) rename crates/solvers/src/tests/{baseline => cases}/buy_order_rounding.rs (100%) rename crates/solvers/src/tests/{baseline => cases}/direct_swap.rs (100%) rename crates/solvers/src/tests/{baseline => cases}/internalization.rs (100%) rename crates/solvers/src/tests/{baseline => cases}/limit_order_quoting.rs (100%) rename crates/solvers/src/tests/{baseline => cases}/mod.rs (100%) rename crates/solvers/src/tests/{baseline => cases}/partial_fill.rs (100%) delete mode 100644 crates/solvers/src/tests/naive/extract_deepest_pool.rs delete mode 100644 crates/solvers/src/tests/naive/filters_out_of_price_orders.rs delete mode 100644 crates/solvers/src/tests/naive/limit_order_price.rs delete mode 100644 crates/solvers/src/tests/naive/matches_orders.rs delete mode 100644 crates/solvers/src/tests/naive/mod.rs delete mode 100644 crates/solvers/src/tests/naive/reserves_too_small.rs delete mode 100644 crates/solvers/src/tests/naive/rounds_prices_in_favour_of_traders.rs delete mode 100644 crates/solvers/src/tests/naive/swap_less_than_reserves.rs delete mode 100644 crates/solvers/src/tests/naive/without_pool.rs diff --git a/README.md b/README.md index 844da11f15..016bcf40d6 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,6 @@ Concretely, it is responsible for "cutting" new auctions (i.e. determining aucti The `autopilot` connects to the same PostgreSQL database as the `orderbook` and uses it to query orders as well as storing the most recent auction and settlement competition. -## Solver - -The `solver` crate is responsible for submitting on-chain settlements based on the orders it gets from the order book and other liquidity sources like Balancer or Uniswap pools. - -It implements a few settlement strategies directly in Rust: - -- Naive Solver: Can match to overlapping opposing orders (e.g. DAI for WETH & WETH for DAI) with one another settling the excess with Uniswap -- Uniswap Baseline: Same path finding as used by the Uniswap frontend (settling orders individually instead of batching them together) - -It can also interact with a more advanced, Gnosis internal, closed source solver which tries to settle all orders using the combinatorial optimization formulations described in [Multi-Token Batch Auctions with Uniform Clearing Price](https://github.com/gnosis/dex-research/blob/master/BatchAuctionOptimization/batchauctions.pdf) - ## Other Crates There are additional crates that live in the cargo workspace. diff --git a/crates/solver/src/settlement/mod.rs b/crates/solver/src/settlement/mod.rs index 01dda9c696..a63db21a85 100644 --- a/crates/solver/src/settlement/mod.rs +++ b/crates/solver/src/settlement/mod.rs @@ -131,14 +131,6 @@ impl Settlement { self.encoder.clearing_prices() } - /// Returns the clearing price for the specified token. - /// - /// Returns `None` if the token is not part of the settlement. - #[cfg(test)] - pub(crate) fn clearing_price(&self, token: H160) -> Option { - self.clearing_prices().get(&token).copied() - } - /// Returns all orders included in the settlement. pub fn traded_orders(&self) -> impl Iterator + '_ { self.encoder.all_trades().map(|trade| &trade.data.order) diff --git a/crates/solver/src/solver/baseline_solver.rs b/crates/solver/src/solver.rs similarity index 73% rename from crates/solver/src/solver/baseline_solver.rs rename to crates/solver/src/solver.rs index be79d85a0e..3c694e7048 100644 --- a/crates/solver/src/solver/baseline_solver.rs +++ b/crates/solver/src/solver.rs @@ -1,12 +1,32 @@ use { crate::liquidity::{ConstantProductOrder, WeightedProductOrder}, + anyhow::anyhow, ethcontract::{H160, U256}, shared::{ baseline_solver::BaselineSolvable, sources::{balancer_v2::swap::WeightedPoolRef, uniswap_v2::pool_fetching::Pool}, }, + std::{fmt::Debug, str::FromStr}, }; +// Wrapper type for AWS ARN identifiers +#[derive(Debug, Clone)] +pub struct Arn(pub String); + +impl FromStr for Arn { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + // Could be more strict here, but this should suffice to catch unintended + // configuration mistakes + if s.starts_with("arn:aws:kms:") { + Ok(Self(s.to_string())) + } else { + Err(anyhow!("Invalid ARN identifier: {}", s)) + } + } +} + impl BaselineSolvable for ConstantProductOrder { fn get_amount_out(&self, out_token: H160, input: (U256, H160)) -> Option { amm_to_pool(self).get_amount_out(out_token, input) diff --git a/crates/solver/src/solver/mod.rs b/crates/solver/src/solver/mod.rs deleted file mode 100644 index e285094723..0000000000 --- a/crates/solver/src/solver/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -use { - anyhow::anyhow, - std::{fmt::Debug, str::FromStr}, -}; - -mod baseline_solver; -pub mod naive_solver; - -// Wrapper type for AWS ARN identifiers -#[derive(Debug, Clone)] -pub struct Arn(pub String); - -impl FromStr for Arn { - type Err = anyhow::Error; - - fn from_str(s: &str) -> std::result::Result { - // Could be more strict here, but this should suffice to catch unintended - // configuration mistakes - if s.starts_with("arn:aws:kms:") { - Ok(Self(s.to_string())) - } else { - Err(anyhow!("Invalid ARN identifier: {}", s)) - } - } -} diff --git a/crates/solver/src/solver/naive_solver.rs b/crates/solver/src/solver/naive_solver.rs deleted file mode 100644 index dc0fba6243..0000000000 --- a/crates/solver/src/solver/naive_solver.rs +++ /dev/null @@ -1,1086 +0,0 @@ -use { - crate::{ - liquidity::{self, slippage::SlippageContext, LimitOrderExecution}, - settlement::{PricedTrade, Settlement}, - }, - anyhow::Result, - liquidity::{AmmOrderExecution, ConstantProductOrder, LimitOrder}, - model::order::OrderKind, - num::{rational::Ratio, BigInt, BigRational, CheckedDiv}, - number::conversions::{big_int_to_u256, big_rational_to_u256, u256_to_big_int}, - primitive_types::U256, - shared::{ - conversions::{RatioExt, U256Ext}, - http_solver::model::TokenAmount, - }, - std::collections::HashMap, - web3::types::Address, -}; - -#[derive(Debug, Clone)] -struct TokenContext { - address: Address, - reserve: U256, - buy_volume: U256, - sell_volume: U256, -} - -impl TokenContext { - pub fn is_excess_after_fees(&self, deficit: &TokenContext, fee: Ratio) -> bool { - fee.denom() - * u256_to_big_int(&self.reserve) - * (u256_to_big_int(&deficit.sell_volume) - u256_to_big_int(&deficit.buy_volume)) - < (fee.denom() - fee.numer()) - * u256_to_big_int(&deficit.reserve) - * (u256_to_big_int(&self.sell_volume) - u256_to_big_int(&self.buy_volume)) - } - - pub fn is_excess_before_fees(&self, deficit: &TokenContext) -> bool { - u256_to_big_int(&self.reserve) - * (u256_to_big_int(&deficit.sell_volume) - u256_to_big_int(&deficit.buy_volume)) - < u256_to_big_int(&deficit.reserve) - * (u256_to_big_int(&self.sell_volume) - u256_to_big_int(&self.buy_volume)) - } -} - -pub fn solve( - slippage: &SlippageContext, - orders: impl IntoIterator, - pool: &ConstantProductOrder, -) -> Option { - let mut orders: Vec = orders.into_iter().collect(); - while !orders.is_empty() { - let (context_a, context_b) = split_into_contexts(&orders, pool); - if let Some(valid_solution) = - solve_orders(slippage, &orders, pool, &context_a, &context_b).filter(is_valid_solution) - { - return Some(valid_solution); - } else { - // remove order with worst limit price that is selling excess token (to make it - // less excessive) and try again - let excess_token = if context_a.is_excess_before_fees(&context_b) { - context_a.address - } else { - context_b.address - }; - let order_to_remove = orders - .iter() - .enumerate() - .filter(|o| o.1.sell_token == excess_token) - .max_by(|lhs, rhs| { - (lhs.1.buy_amount * rhs.1.sell_amount) - .cmp(&(lhs.1.sell_amount * rhs.1.buy_amount)) - }); - match order_to_remove { - Some((index, _)) => orders.swap_remove(index), - None => break, - }; - } - } - - None -} - -/// -/// Computes a settlement using orders of a single pair and the direct AMM -/// between those tokens.get(. Panics if orders are not already filtered for a -/// specific token pair, or the reserve information for that pair is not -/// available. -fn solve_orders( - slippage: &SlippageContext, - orders: &[LimitOrder], - pool: &ConstantProductOrder, - context_a: &TokenContext, - context_b: &TokenContext, -) -> Option { - if context_a.is_excess_after_fees(context_b, pool.fee) { - solve_with_uniswap(slippage, orders, pool, context_b, context_a) - } else if context_b.is_excess_after_fees(context_a, pool.fee) { - solve_with_uniswap(slippage, orders, pool, context_a, context_b) - } else { - solve_without_uniswap(orders, context_a, context_b).ok() - } -} - -/// -/// Creates a solution using the current AMM spot price, without using any of -/// its liquidity -fn solve_without_uniswap( - orders: &[LimitOrder], - context_a: &TokenContext, - context_b: &TokenContext, -) -> Result { - let mut settlement = Settlement::new(maplit::hashmap! { - context_a.address => context_b.reserve, - context_b.address => context_a.reserve, - }); - for order in orders { - let execution = LimitOrderExecution { - filled: order.full_execution_amount(), - fee: order.user_fee, - }; - settlement.with_liquidity(order, execution)?; - } - - Ok(settlement) -} - -/// -/// Creates a solution using the current AMM's liquidity to balance excess and -/// shortage. The clearing price is the effective exchange rate used by the AMM -/// interaction. -fn solve_with_uniswap( - slippage: &SlippageContext, - orders: &[LimitOrder], - pool: &ConstantProductOrder, - shortage: &TokenContext, - excess: &TokenContext, -) -> Option { - let uniswap_in_token = excess.address; - let uniswap_out_token = shortage.address; - - let uniswap_out = compute_uniswap_out(shortage, excess, pool.fee)?; - let uniswap_in = compute_uniswap_in(uniswap_out.clone(), shortage, excess, pool.fee)?; - - let uniswap_out = big_rational_to_u256(&uniswap_out).ok()?; - let uniswap_in = big_rational_to_u256(&uniswap_in).ok()?; - - let mut settlement = Settlement::new(maplit::hashmap! { - uniswap_in_token => uniswap_out, - uniswap_out_token => uniswap_in, - }); - for order in orders { - let execution = LimitOrderExecution { - filled: order.full_execution_amount(), - // TODO: We still need to compute a fee for partially fillable limit orders. - fee: order.user_fee, - }; - settlement.with_liquidity(order, execution).ok()?; - } - - // Because the smart contracts round in the favour of the traders, it could - // be that we actually require a bit more from the Uniswap pool in order to - // pay out all proceeds. We move the rounding error to the sell token so that - // it either comes out of the fees or existing buffers. It is also important - // to use the **synthetic** order amounts for this, as this output amount - // needs to be computed for what is actually intented to be traded against - // an AMM and not how the order will be executed (including things like - // surplus fees). - let uniswap_out_required_by_orders = big_int_to_u256(&orders.iter().try_fold( - BigInt::default(), - |mut total, order| { - if order.sell_token == uniswap_out_token { - total -= match order.kind { - OrderKind::Sell => order.sell_amount, - OrderKind::Buy => order - .buy_amount - .checked_mul(uniswap_out)? - .checked_div(uniswap_in)?, - } - .to_big_int(); - } else { - total += match order.kind { - OrderKind::Sell => order - .sell_amount - .checked_mul(uniswap_out)? - .checked_ceil_div(&uniswap_in)?, - OrderKind::Buy => order.buy_amount, - } - .to_big_int(); - }; - Some(total) - }, - )?) - .ok()?; - - // In theory, not all limit orders are GPv2 trades (although in practice - // they are). Since those orders don't generate trades, they aren't - // considered in the `uniswap_out_with_rounding` computation. Luckily, they - // don't get any surplus anyway, so we can just use the original output - // amount as the rounding error will come from the surplus that those orders - // would have otherwise received. - let uniswap_out_with_rounding = uniswap_out_required_by_orders.max(uniswap_out); - - settlement - .with_liquidity( - pool, - slippage - .apply_to_amm_execution(AmmOrderExecution { - input_max: TokenAmount::new(uniswap_in_token, uniswap_in), - output: TokenAmount::new(uniswap_out_token, uniswap_out_with_rounding), - internalizable: false, - }) - .ok()?, - ) - .ok()?; - - Some(settlement) -} - -impl ConstantProductOrder { - fn get_reserve(&self, token: &Address) -> Option { - if &self.tokens.get().0 == token { - Some(self.reserves.0.into()) - } else if &self.tokens.get().1 == token { - Some(self.reserves.1.into()) - } else { - None - } - } -} - -fn split_into_contexts( - orders: &[LimitOrder], - pool: &ConstantProductOrder, -) -> (TokenContext, TokenContext) { - let mut contexts = HashMap::new(); - for order in orders { - let buy_context = contexts - .entry(order.buy_token) - .or_insert_with(|| TokenContext { - address: order.buy_token, - reserve: pool - .get_reserve(&order.buy_token) - .unwrap_or_else(|| panic!("No reserve for token {}", &order.buy_token)), - buy_volume: U256::zero(), - sell_volume: U256::zero(), - }); - if matches!(order.kind, OrderKind::Buy) { - buy_context.buy_volume += order.buy_amount - } - - let sell_context = contexts - .entry(order.sell_token) - .or_insert_with(|| TokenContext { - address: order.sell_token, - reserve: pool - .get_reserve(&order.sell_token) - .unwrap_or_else(|| panic!("No reserve for token {}", &order.sell_token)), - buy_volume: U256::zero(), - sell_volume: U256::zero(), - }); - if matches!(order.kind, OrderKind::Sell) { - sell_context.sell_volume += order.sell_amount - } - } - assert_eq!(contexts.len(), 2, "Orders contain more than two tokens"); - let mut contexts = contexts.drain().map(|(_, v)| v); - (contexts.next().unwrap(), contexts.next().unwrap()) -} - -/// -/// Given information about the shortage token (the one we need to take from -/// Uniswap) and the excess token (the one we give to Uniswap), this function -/// computes the exact out_amount required from Uniswap to perfectly match -/// demand and supply at the effective Uniswap price (the one used for that -/// in/out swap). -/// -/// The derivation of this formula is described in https://docs.google.com/document/d/1jS22wxbCqo88fGsqEMZgRQgiAcHlPqxoMw3CJTHst6c/edit -/// It assumes GP fee (φ) to be 1 -fn compute_uniswap_out( - shortage: &TokenContext, - excess: &TokenContext, - amm_fee: Ratio, -) -> Option { - let numerator_minuend = (amm_fee.denom() - amm_fee.numer()) - * (u256_to_big_int(&excess.sell_volume) - u256_to_big_int(&excess.buy_volume)) - * u256_to_big_int(&shortage.reserve); - let numerator_subtrahend = amm_fee.denom() - * (u256_to_big_int(&shortage.sell_volume) - u256_to_big_int(&shortage.buy_volume)) - * u256_to_big_int(&excess.reserve); - let denominator: BigInt = amm_fee.denom() * u256_to_big_int(&excess.reserve) - + (amm_fee.denom() - amm_fee.numer()) - * (u256_to_big_int(&excess.sell_volume) - u256_to_big_int(&excess.buy_volume)); - BigRational::new_checked(numerator_minuend - numerator_subtrahend, denominator).ok() -} - -/// -/// Given the desired amount to receive and the state of the pool, this computes -/// the required amount of tokens to be sent to the pool. -/// Taken from: https://github.com/Uniswap/uniswap-v2-periphery/blob/4123f93278b60bcf617130629c69d4016f9e7584/contracts/libraries/UniswapV2Library.sol#L53 -/// Not adding + 1 in the end, because we are working with rationals and thus -/// don't round up. -fn compute_uniswap_in( - out: BigRational, - shortage: &TokenContext, - excess: &TokenContext, - amm_fee: Ratio, -) -> Option { - let numerator = U256::from(*amm_fee.denom()).to_big_rational() - * out.clone() - * u256_to_big_int(&excess.reserve); - let denominator = U256::from(amm_fee.denom() - amm_fee.numer()).to_big_rational() - * (shortage.reserve.to_big_rational() - out); - numerator.checked_div(&denominator) -} - -/// -/// Returns true if for each trade the executed price is not smaller than the -/// limit price Thus we ensure that `buy_token_price / sell_token_price >= -/// limit_buy_amount / limit_sell_amount` -fn is_valid_solution(solution: &Settlement) -> bool { - for PricedTrade { - data, - sell_token_price, - buy_token_price, - } in solution.encoder.all_trades() - { - let order = &data.order.data; - - // Check execution respects individual order's limit price - match ( - order.sell_amount.checked_mul(sell_token_price), - order.buy_amount.checked_mul(buy_token_price), - ) { - (Some(sell_volume), Some(buy_volume)) if sell_volume >= buy_volume => (), - _ => return false, - } - - // Check individual order's execution price satisfies uniform clearing price - // E.g. liquidity orders may have a different executed price. - let clearing_prices = solution.encoder.clearing_prices(); - match ( - clearing_prices - .get(&order.buy_token) - .map(|clearing_sell_price| clearing_sell_price.checked_mul(sell_token_price)), - clearing_prices - .get(&order.sell_token) - .map(|clearing_buy_price| clearing_buy_price.checked_mul(buy_token_price)), - ) { - (Some(execution_sell_value), Some(clearing_buy_value)) - if execution_sell_value <= clearing_buy_value => {} - _ => return false, - } - } - - true -} - -#[cfg(test)] -mod tests { - use { - super::*, - crate::liquidity::slippage::SlippageCalculator, - ethcontract::H160, - liquidity::tests::CapturingSettlementHandler, - maplit::hashmap, - model::{ - order::{Order, OrderData}, - TokenPair, - }, - num::rational::Ratio, - once_cell::sync::OnceCell, - shared::{ - baseline_solver::BaselineSolvable, - external_prices::ExternalPrices, - sources::uniswap_v2::pool_fetching::Pool, - }, - }; - - fn to_wei(base: u128) -> U256 { - U256::from(base) * U256::from(10).pow(18.into()) - } - - fn without_slippage() -> SlippageContext<'static> { - static CONTEXT: OnceCell<(ExternalPrices, SlippageCalculator)> = OnceCell::new(); - let (prices, calculator) = - CONTEXT.get_or_init(|| (Default::default(), SlippageCalculator::from_bps(0, None))); - calculator.context(prices) - } - - #[test] - fn finds_clearing_price_with_sell_orders_on_both_sides() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - LimitOrder { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(40), - buy_amount: to_wei(30), - kind: OrderKind::Sell, - id: 0.into(), - ..Default::default() - }, - LimitOrder { - sell_token: token_b, - buy_token: token_a, - sell_amount: to_wei(100), - buy_amount: to_wei(90), - kind: OrderKind::Sell, - id: 1.into(), - ..Default::default() - }, - ]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (to_wei(1000).as_u128(), to_wei(1000).as_u128()), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler.clone(), - }; - let result = solve(&without_slippage(), orders.clone(), &pool).unwrap(); - - // Make sure the uniswap interaction is using the correct direction - let interaction = amm_handler.calls()[0].clone(); - assert_eq!(interaction.input_max.token, token_b); - assert_eq!(interaction.output.token, token_a); - - // Make sure the sell amounts +/- uniswap interaction satisfy min_buy amounts - assert!(orders[0].sell_amount + interaction.output.amount >= orders[1].buy_amount); - assert!(orders[1].sell_amount - interaction.input_max.amount > orders[0].buy_amount); - - // Make sure the sell amounts +/- uniswap interaction satisfy expected buy - // amounts given clearing price - let price_a = result.clearing_price(token_a).unwrap(); - let price_b = result.clearing_price(token_b).unwrap(); - - // Multiplying sellAmount with priceA, gives us sell value in "$", divided by - // priceB gives us value in buy token We should have at least as much to - // give (sell amount +/- uniswap) as is expected by the buyer - let expected_buy = (orders[0].sell_amount * price_a).ceil_div(&price_b); - assert!(orders[1].sell_amount - interaction.input_max.amount >= expected_buy); - - let expected_buy = (orders[1].sell_amount * price_b).ceil_div(&price_a); - assert!(orders[0].sell_amount + interaction.input_max.amount >= expected_buy); - } - - #[test] - fn finds_clearing_price_with_sell_orders_on_one_side() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - LimitOrder { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(40), - buy_amount: to_wei(30), - kind: OrderKind::Sell, - id: 0.into(), - ..Default::default() - }, - LimitOrder { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(100), - buy_amount: to_wei(90), - kind: OrderKind::Sell, - id: 1.into(), - ..Default::default() - }, - ]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (to_wei(1_000_000).as_u128(), to_wei(1_000_000).as_u128()), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler.clone(), - }; - let result = solve(&without_slippage(), orders.clone(), &pool).unwrap(); - - // Make sure the uniswap interaction is using the correct direction - let interaction = amm_handler.calls()[0].clone(); - assert_eq!(interaction.input_max.token, token_a); - assert_eq!(interaction.output.token, token_b); - - // Make sure the sell amounts cover the uniswap in, and min buy amounts are - // covered by uniswap out - assert!(orders[0].sell_amount + orders[1].sell_amount >= interaction.input_max.amount); - assert!(interaction.output.amount >= orders[0].buy_amount + orders[1].buy_amount); - - // Make sure expected buy amounts (given prices) are also covered by uniswap out - // amounts - let price_a = result.clearing_price(token_a).unwrap(); - let price_b = result.clearing_price(token_b).unwrap(); - - let first_expected_buy = orders[0].sell_amount * price_a / price_b; - let second_expected_buy = orders[1].sell_amount * price_a / price_b; - assert!(interaction.output.amount >= first_expected_buy + second_expected_buy); - } - - #[test] - fn finds_clearing_price_with_buy_orders_on_both_sides() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - LimitOrder { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(40), - buy_amount: to_wei(30), - kind: OrderKind::Buy, - id: 0.into(), - ..Default::default() - }, - LimitOrder { - sell_token: token_b, - buy_token: token_a, - sell_amount: to_wei(100), - buy_amount: to_wei(90), - kind: OrderKind::Buy, - id: 1.into(), - ..Default::default() - }, - ]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (to_wei(1000).as_u128(), to_wei(1000).as_u128()), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler.clone(), - }; - let result = solve(&SlippageContext::default(), orders.clone(), &pool).unwrap(); - - // Make sure the uniswap interaction is using the correct direction - let interaction = amm_handler.calls()[0].clone(); - assert_eq!(interaction.input_max.token, token_b); - assert_eq!(interaction.output.token, token_a); - - // Make sure the buy amounts +/- uniswap interaction satisfy max_sell amounts - assert!(orders[0].sell_amount >= orders[1].buy_amount - interaction.output.amount); - assert!(orders[1].sell_amount >= orders[0].buy_amount + interaction.input_max.amount); - - // Make sure buy sell amounts +/- uniswap interaction satisfy expected sell - // amounts given clearing price - let price_a = result.clearing_price(token_a).unwrap(); - let price_b = result.clearing_price(token_b).unwrap(); - - // Multiplying buyAmount with priceB, gives us sell value in "$", divided by - // priceA gives us value in sell token The seller should expect to sell - // at least as much as we require for the buyer + uniswap. - let expected_sell = orders[0].buy_amount * price_b / price_a; - assert!(orders[1].buy_amount - interaction.input_max.amount <= expected_sell); - - let expected_sell = orders[1].buy_amount * price_a / price_b; - assert!(orders[0].buy_amount + interaction.output.amount <= expected_sell); - } - - #[test] - fn finds_clearing_price_with_buy_orders_and_sell_orders() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - LimitOrder { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(40), - buy_amount: to_wei(30), - kind: OrderKind::Buy, - id: 0.into(), - ..Default::default() - }, - LimitOrder { - sell_token: token_b, - buy_token: token_a, - sell_amount: to_wei(100), - buy_amount: to_wei(90), - kind: OrderKind::Sell, - id: 1.into(), - ..Default::default() - }, - ]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (to_wei(1000).as_u128(), to_wei(1000).as_u128()), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler.clone(), - }; - let result = solve(&SlippageContext::default(), orders.clone(), &pool).unwrap(); - - // Make sure the uniswap interaction is using the correct direction - let interaction = amm_handler.calls()[0].clone(); - assert_eq!(interaction.input_max.token, token_b); - assert_eq!(interaction.output.token, token_a); - - // Make sure the buy order's sell amount - uniswap interaction satisfies sell - // order's limit - assert!(orders[0].sell_amount >= orders[1].buy_amount - interaction.output.amount); - - // Make sure the sell order's buy amount + uniswap interaction satisfies buy - // order's limit - assert!(orders[1].buy_amount + interaction.input_max.amount >= orders[0].sell_amount); - - // Make sure buy sell amounts +/- uniswap interaction satisfy expected sell - // amounts given clearing price - let price_a = result.clearing_price(token_a).unwrap(); - let price_b = result.clearing_price(token_b).unwrap(); - - // Multiplying buy_amount with priceB, gives us sell value in "$", divided by - // priceA gives us value in sell token The seller should expect to sell - // at least as much as we require for the buyer + uniswap. - let expected_sell = orders[0].buy_amount * price_b / price_a; - assert!(orders[1].buy_amount - interaction.input_max.amount <= expected_sell); - - // Multiplying sell_amount with priceA, gives us sell value in "$", divided by - // priceB gives us value in buy token We should have at least as much to - // give (sell amount + uniswap out) as is expected by the buyer - let expected_buy = orders[1].sell_amount * price_b / price_a; - assert!(orders[0].sell_amount + interaction.output.amount >= expected_buy); - } - - #[test] - fn finds_clearing_without_using_uniswap() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - LimitOrder { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(1001), - buy_amount: to_wei(1000), - kind: OrderKind::Sell, - id: 0.into(), - ..Default::default() - }, - LimitOrder { - sell_token: token_b, - buy_token: token_a, - sell_amount: to_wei(1001), - buy_amount: to_wei(1000), - kind: OrderKind::Sell, - id: 1.into(), - ..Default::default() - }, - ]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (to_wei(1_000_001).as_u128(), to_wei(1_000_000).as_u128()), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler.clone(), - }; - let result = solve(&SlippageContext::default(), orders, &pool).unwrap(); - assert!(amm_handler.calls().is_empty()); - assert_eq!( - result.clearing_prices(), - &maplit::hashmap! { - token_a => to_wei(1_000_000), - token_b => to_wei(1_000_001) - } - ); - } - - #[test] - fn finds_solution_excluding_orders_whose_limit_price_is_not_satisfiable() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - // Unreasonable order a -> b - Order { - data: OrderData { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(1), - buy_amount: to_wei(1000), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - // Reasonable order a -> b - Order { - data: OrderData { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(1000), - buy_amount: to_wei(1000), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - // Reasonable order b -> a - Order { - data: OrderData { - sell_token: token_b, - buy_token: token_a, - sell_amount: to_wei(1000), - buy_amount: to_wei(1000), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - // Unreasonable order b -> a - Order { - data: OrderData { - sell_token: token_b, - buy_token: token_a, - sell_amount: to_wei(2), - buy_amount: to_wei(1000), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - ]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (to_wei(1_000_000).as_u128(), to_wei(1_000_000).as_u128()), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler, - }; - let result = solve(&SlippageContext::default(), orders, &pool).unwrap(); - - assert_eq!(result.traded_orders().count(), 2); - assert!(is_valid_solution(&result)); - } - - #[test] - fn returns_empty_solution_if_orders_have_no_overlap() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - Order { - data: OrderData { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(900), - buy_amount: to_wei(1000), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - Order { - data: OrderData { - sell_token: token_b, - buy_token: token_a, - sell_amount: to_wei(900), - buy_amount: to_wei(1000), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - ]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (to_wei(1_000_001).as_u128(), to_wei(1_000_000).as_u128()), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler, - }; - assert!(solve(&SlippageContext::default(), orders, &pool).is_none()); - } - - #[test] - fn test_is_valid_solution() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - Order { - data: OrderData { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(10), - buy_amount: to_wei(8), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - }, - Order { - data: OrderData { - sell_token: token_b, - buy_token: token_a, - sell_amount: to_wei(10), - buy_amount: to_wei(9), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - }, - ]; - - let settlement_with_prices = |prices: HashMap| { - let mut settlement = Settlement::new(prices); - for order in orders.iter().cloned() { - let limit_order = LimitOrder::from(order); - let execution = LimitOrderExecution::new( - limit_order.full_execution_amount(), - limit_order.user_fee, - ); - settlement.with_liquidity(&limit_order, execution).unwrap(); - } - settlement - }; - - // Price in the middle is ok - assert!(is_valid_solution(&settlement_with_prices( - maplit::hashmap! { - token_a => to_wei(1), - token_b => to_wei(1) - } - ),)); - - // Price at the limit of first order is ok - assert!(is_valid_solution(&settlement_with_prices( - maplit::hashmap! { - token_a => to_wei(8), - token_b => to_wei(10) - } - ),)); - - // Price at the limit of second order is ok - assert!(is_valid_solution(&settlement_with_prices( - maplit::hashmap! { - token_a => to_wei(10), - token_b => to_wei(9) - } - ),)); - - // Price violating first order is not ok - assert!(!is_valid_solution(&settlement_with_prices( - maplit::hashmap! { - token_a => to_wei(7), - token_b => to_wei(10) - } - ),)); - - // Price violating second order is not ok - assert!(!is_valid_solution(&settlement_with_prices( - maplit::hashmap! { - token_a => to_wei(10), - token_b => to_wei(8) - } - ),)); - } - - #[test] - fn does_not_panic() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - Order { - data: OrderData { - sell_token: token_a, - buy_token: token_b, - sell_amount: U256::MAX, - buy_amount: 1.into(), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - Order { - data: OrderData { - sell_token: token_b, - buy_token: token_a, - sell_amount: 1.into(), - buy_amount: 1.into(), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - ]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (u128::MAX, u128::MAX), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler, - }; - // This line should not panic. - solve(&SlippageContext::default(), orders, &pool); - } - - #[test] - fn reserves_are_too_small() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - Order { - data: OrderData { - sell_token: token_a, - buy_token: token_b, - sell_amount: 70145218378783248142575u128.into(), - buy_amount: 70123226323u128.into(), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - Order { - data: OrderData { - sell_token: token_a, - buy_token: token_b, - sell_amount: 900_000_000_000_000u128.into(), - buy_amount: 100.into(), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - ]; - // Reserves are much smaller than buy amount - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (25000075, 2500007500), - fee: Ratio::new(3, 1000), - settlement_handling: CapturingSettlementHandler::arc(), - }; - - // The first order by itself should not be matchable. - assert!(solve(&SlippageContext::default(), orders[0..1].to_vec(), &pool).is_none()); - - // Only the second order should match - let result = solve(&SlippageContext::default(), orders, &pool).unwrap(); - assert_eq!(result.traded_orders().count(), 1); - } - - #[test] - fn rounds_prices_in_favour_of_traders() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![ - Order { - data: OrderData { - sell_token: token_a, - buy_token: token_b, - sell_amount: 9_000_000.into(), - buy_amount: 8_500_000.into(), - kind: OrderKind::Sell, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - Order { - data: OrderData { - buy_token: token_a, - sell_token: token_b, - buy_amount: 8_000_001.into(), - sell_amount: 8_500_000.into(), - kind: OrderKind::Buy, - partially_fillable: false, - ..Default::default() - }, - ..Default::default() - } - .into(), - ]; - - let pool = Pool::uniswap( - H160::from_low_u64_be(1), - TokenPair::new(token_a, token_b).unwrap(), - (1_000_000_000_000_000_000, 1_000_000_000_000_000_000), - ); - - // Note that these orders have an excess of ~1e6 T_a because the first - // order is a sell order selling 9e6 T_a and the second order is buying - // 8e6 T_a. Use this to compute the exact amount that will get swapped - // with the pool. This will define our prices. - let excess_amount_a = 999_999.into(); - let swapped_amount_b = pool - .get_amount_out(token_b, (excess_amount_a, token_a)) - .unwrap(); - - // The naive solver sets the prices to the the swapped amounts, so: - let expected_prices = hashmap! { - token_a => swapped_amount_b, - token_b => excess_amount_a, - }; - - let settlement = solve(&SlippageContext::default(), orders, &pool.into()).unwrap(); - let trades = settlement.trade_executions().collect::>(); - - // Check the prices are set according to the expected pool swap: - assert_eq!(settlement.clearing_prices(), &expected_prices); - - // Check the executed buy amount gets rounded up for the sell order: - assert_eq!( - trades[0].buy_amount, - U256::from(9_000_000) * expected_prices[&token_a] / expected_prices[&token_b] + 1, - ); - - // Check the executed sell amount gets rounded down for the buy order: - assert_eq!( - trades[1].sell_amount, - U256::from(8_000_001) * expected_prices[&token_a] / expected_prices[&token_b], - ); - } - - #[test] - fn applies_slippage_to_amm_execution() { - let token_a = Address::from_low_u64_be(0); - let token_b = Address::from_low_u64_be(1); - let orders = vec![LimitOrder { - sell_token: token_a, - buy_token: token_b, - sell_amount: to_wei(40), - buy_amount: to_wei(30), - kind: OrderKind::Sell, - id: 0.into(), - ..Default::default() - }]; - - let amm_handler = CapturingSettlementHandler::arc(); - let pool = ConstantProductOrder { - address: H160::from_low_u64_be(1), - tokens: TokenPair::new(token_a, token_b).unwrap(), - reserves: (to_wei(1000).as_u128(), to_wei(1000).as_u128()), - fee: Ratio::new(3, 1000), - settlement_handling: amm_handler.clone(), - }; - let slippage = SlippageContext::default(); - solve(&slippage, orders, &pool).unwrap(); - - assert_eq!( - amm_handler.calls(), - vec![slippage - .apply_to_amm_execution(AmmOrderExecution { - input_max: TokenAmount::new(token_a, to_wei(40)), - output: TokenAmount::new( - token_b, - pool.get_amount_out(token_b, (to_wei(40), token_a)).unwrap() - ), - internalizable: false - }) - .unwrap()], - ); - } -} diff --git a/crates/solvers/src/boundary/baseline.rs b/crates/solvers/src/boundary/baseline.rs index 51a4a8a2ab..b704475ce2 100644 --- a/crates/solvers/src/boundary/baseline.rs +++ b/crates/solvers/src/boundary/baseline.rs @@ -3,7 +3,7 @@ use { crate::{ boundary, - domain::{eth, liquidity, order, solver::baseline}, + domain::{eth, liquidity, order, solver}, }, ethereum_types::{H160, U256}, model::TokenPair, @@ -33,11 +33,7 @@ impl<'a> Solver<'a> { } } - pub fn route( - &self, - request: baseline::Request, - max_hops: usize, - ) -> Option> { + pub fn route(&self, request: solver::Request, max_hops: usize) -> Option> { let candidates = self.base_tokens.path_candidates_with_hops( request.sell.token.0, request.buy.token.0, @@ -95,7 +91,7 @@ impl<'a> Solver<'a> { .max_by_key(|(_, buy)| buy.value)?, }; - baseline::Route::new(segments) + solver::Route::new(segments) } fn traverse_path( @@ -103,7 +99,7 @@ impl<'a> Solver<'a> { path: &[&OnchainLiquidity], mut sell_token: H160, mut sell_amount: U256, - ) -> Option>> { + ) -> Option>> { let mut segments = Vec::new(); for liquidity in path { let reference_liquidity = self @@ -117,7 +113,7 @@ impl<'a> Solver<'a> { .expect("Inconsistent path"); let buy_amount = liquidity.get_amount_out(buy_token, (sell_amount, sell_token))?; - segments.push(baseline::Segment { + segments.push(solver::Segment { liquidity: reference_liquidity, input: eth::Asset { token: eth::TokenAddress(sell_token), diff --git a/crates/solvers/src/boundary/mod.rs b/crates/solvers/src/boundary/mod.rs index 74319cdcdd..3be2864e8d 100644 --- a/crates/solvers/src/boundary/mod.rs +++ b/crates/solvers/src/boundary/mod.rs @@ -3,6 +3,5 @@ pub mod baseline; pub mod liquidity; -pub mod naive; pub type Result = anyhow::Result; diff --git a/crates/solvers/src/boundary/naive.rs b/crates/solvers/src/boundary/naive.rs deleted file mode 100644 index 5a463040b2..0000000000 --- a/crates/solvers/src/boundary/naive.rs +++ /dev/null @@ -1,249 +0,0 @@ -use { - crate::{ - boundary::liquidity::constant_product::to_boundary_pool, - domain::{ - eth, - liquidity, - order, - solution::{self}, - }, - }, - ethereum_types::H160, - itertools::Itertools, - model::order::{Order, OrderClass, OrderData, OrderKind, OrderMetadata, OrderUid}, - num::{BigRational, One}, - shared::external_prices::ExternalPrices, - solver::{ - liquidity::{ - slippage::{SlippageCalculator, SlippageContext}, - AmmOrderExecution, - ConstantProductOrder, - Exchange, - LimitOrder, - LimitOrderExecution, - LimitOrderId, - SettlementHandling, - }, - settlement::SettlementEncoder, - solver::naive_solver, - }, - std::sync::{Arc, Mutex}, -}; - -pub fn solve( - orders: &[&order::Order], - liquidity: &liquidity::Liquidity, -) -> Option { - let pool = match &liquidity.state { - liquidity::State::ConstantProduct(pool) => pool, - _ => return None, - }; - - // Note that the `order::Order` -> `boundary::LimitOrder` mapping here is - // not exact. Among other things, the signature and various signed order - // fields are missing from the `order::Order` data that the solver engines - // have access to. This means that the naive solver in the `solver` crate - // will encode "incorrect" settlements. This is fine, since we give it just - // enough data to compute the correct swapped orders and the swap amounts - // which is what the naive solver in the `solvers` crate cares about. The - // `driver` is then responsible for encoding the solution into a valid - // settlement transaction anyway. - let boundary_orders = orders - .iter() - // The naive solver currently doesn't support limit orders, so filter them out. - .filter(|order| !order.solver_determines_fee()) - .map(|order| LimitOrder { - id: match order.class { - order::Class::Market => LimitOrderId::Market(OrderUid(order.uid.0)), - order::Class::Limit => LimitOrderId::Limit(OrderUid(order.uid.0)), - }, - sell_token: order.sell.token.0, - buy_token: order.buy.token.0, - sell_amount: order.sell.amount, - buy_amount: order.buy.amount, - kind: match order.side { - order::Side::Buy => OrderKind::Buy, - order::Side::Sell => OrderKind::Sell, - }, - partially_fillable: order.partially_fillable, - user_fee: 0.into(), - settlement_handling: Arc::new(OrderHandler { - order: Order { - metadata: OrderMetadata { - uid: OrderUid(order.uid.0), - class: match order.class { - order::Class::Market => OrderClass::Market, - order::Class::Limit => OrderClass::Limit, - }, - solver_fee: 0.into(), - ..Default::default() - }, - data: OrderData { - sell_token: order.sell.token.0, - buy_token: order.buy.token.0, - sell_amount: order.sell.amount, - buy_amount: order.buy.amount, - fee_amount: 0.into(), - kind: match order.side { - order::Side::Buy => OrderKind::Buy, - order::Side::Sell => OrderKind::Sell, - }, - partially_fillable: order.partially_fillable, - ..Default::default() - }, - ..Default::default() - }, - }), - exchange: Exchange::GnosisProtocol, - }) - .collect_vec(); - - let slippage = Slippage::new(pool.tokens()); - let pool_handler = Arc::new(PoolHandler::default()); - let boundary_pool = ConstantProductOrder::for_pool( - to_boundary_pool(liquidity.address, pool)?, - pool_handler.clone(), - ); - - let boundary_solution = - naive_solver::solve(&slippage.context(), boundary_orders, &boundary_pool)?; - - let swap = pool_handler.swap.lock().unwrap().take(); - Some(solution::Solution { - id: Default::default(), - prices: solution::ClearingPrices::new( - boundary_solution - .clearing_prices() - .iter() - .map(|(token, price)| (eth::TokenAddress(*token), *price)), - ), - trades: boundary_solution - .traded_orders() - .map(|order| { - solution::Trade::Fulfillment( - solution::Fulfillment::fill( - orders - .iter() - .copied() - .find(|o| o.uid.0 == order.metadata.uid.0) - .unwrap() - .clone(), - ) - .expect("all orders can be filled, as limit orders are filtered out"), - ) - }) - .collect(), - // We can skip computing a gas estimate here because that is only used by the protocol - // when quoting trades. And because the naive solver is not able to solve single order - // auctions (which is how we model a /quote request) it can not be used for - // quoting anyway. - gas: None, - pre_interactions: vec![], - interactions: swap - .into_iter() - .map(|(input, output)| { - solution::Interaction::Liquidity(solution::LiquidityInteraction { - liquidity: liquidity.clone(), - input, - output, - internalize: false, - }) - }) - .collect(), - post_interactions: vec![], - }) -} - -// Beyond this point is... well... nameless and boundless chaos. The -// unfathomable horrors that follow are not for the faint of heart! -// -// Joking aside, the existing naive solver implementation is tightly coupled -// with the `Settlement` and `SettlementEncoder` types in the `solver` crate. -// This means that there is no convenient way to say: "please compute a solution -// given this list of orders and constant product pool" without it creating a -// full settlement for encoding. In order to adapt that API into something that -// is useful in this boundary module, we create a fake slippage context that -// applies 0 slippage (so that we can recover the exact executed amounts from -// the constant product pool) and we create capturing settlement handler -// implementations that record the swap that gets added to each settlement so -// that it can be recovered later to build a solution. - -struct Slippage { - calculator: SlippageCalculator, - prices: ExternalPrices, -} - -impl Slippage { - fn new(tokens: liquidity::TokenPair) -> Self { - // We don't actually want to include slippage yet. This is because the - // Naive solver encodes liquidity interactions and the driver is - // responsible for applying slippage to those. Create a dummy slippage - // context for use with the legacy Naive solver. - let (token0, token1) = tokens.get(); - Self { - calculator: SlippageCalculator::from_bps(0, None), - prices: ExternalPrices::new( - H160::default(), - [ - (token0.0, BigRational::one()), - (token1.0, BigRational::one()), - ] - .into_iter() - .collect(), - ) - .unwrap(), - } - } - - fn context(&self) -> SlippageContext { - self.calculator.context(&self.prices) - } -} - -struct OrderHandler { - order: Order, -} - -impl SettlementHandling for OrderHandler { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn encode( - &self, - execution: LimitOrderExecution, - encoder: &mut SettlementEncoder, - ) -> anyhow::Result<()> { - encoder.add_trade(self.order.clone(), execution.filled, execution.fee)?; - Ok(()) - } -} - -#[derive(Default)] -struct PoolHandler { - swap: Mutex>, -} - -impl SettlementHandling for PoolHandler { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn encode( - &self, - execution: AmmOrderExecution, - _: &mut SettlementEncoder, - ) -> anyhow::Result<()> { - *self.swap.lock().unwrap() = Some(( - eth::Asset { - token: eth::TokenAddress(execution.input_max.token), - amount: execution.input_max.amount, - }, - eth::Asset { - token: eth::TokenAddress(execution.output.token), - amount: execution.output.amount, - }, - )); - Ok(()) - } -} diff --git a/crates/solvers/src/domain/solver/baseline.rs b/crates/solvers/src/domain/solver.rs similarity index 97% rename from crates/solvers/src/domain/solver/baseline.rs rename to crates/solvers/src/domain/solver.rs index 83adb83127..9d991d5c60 100644 --- a/crates/solvers/src/domain/solver/baseline.rs +++ b/crates/solvers/src/domain/solver.rs @@ -15,12 +15,13 @@ use { order::{self, Order}, solution, }, + infra::metrics, }, ethereum_types::U256, std::{cmp, collections::HashSet, sync::Arc}, }; -pub struct Baseline(Arc); +pub struct Solver(Arc); /// The amount of time we aim the solver to finish before the final deadline is /// reached. @@ -66,7 +67,7 @@ struct Inner { native_token_price_estimation_amount: eth::U256, } -impl Baseline { +impl Solver { /// Creates a new baseline solver for the specified configuration. pub fn new(config: Config) -> Self { Self(Arc::new(Inner { @@ -82,12 +83,14 @@ impl Baseline { /// Solves the specified auction, returning a vector of all possible /// solutions. pub async fn solve(&self, auction: auction::Auction) -> Vec { + metrics::solve(&auction); + let deadline = auction.deadline.clone(); // Make sure to push the CPU-heavy code to a separate thread in order to // not lock up the [`tokio`] runtime and cause it to slow down handling // the real async things. For larger settlements, this can block in the // 100s of ms. let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); - let deadline = auction + let remaining = auction .deadline .clone() .reduce(DEADLINE_SLACK) @@ -101,7 +104,7 @@ impl Baseline { inner.solve(auction, sender); }; - if tokio::time::timeout(deadline, tokio::spawn(background_work)) + if tokio::time::timeout(remaining, tokio::spawn(background_work)) .await .is_err() { @@ -112,6 +115,7 @@ impl Baseline { while let Ok(solution) = receiver.try_recv() { solutions.push(solution); } + metrics::solved(&deadline, &solutions); solutions } } diff --git a/crates/solvers/src/domain/solver/mod.rs b/crates/solvers/src/domain/solver/mod.rs deleted file mode 100644 index cf4d134583..0000000000 --- a/crates/solvers/src/domain/solver/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::{ - domain::{auction, solution}, - infra::metrics, -}; - -pub mod baseline; -pub mod naive; - -pub use self::{baseline::Baseline, naive::Naive}; - -pub enum Solver { - Baseline(Baseline), - Naive(Naive), -} - -impl Solver { - /// Solves a given auction and returns multiple solutions. We allow - /// returning multiple solutions to later merge multiple non-overlapping - /// solutions to get one big more gas efficient solution. - pub async fn solve(&self, auction: auction::Auction) -> Vec { - metrics::solve(&auction); - let deadline = auction.deadline.clone(); - let solutions = match self { - Solver::Baseline(solver) => solver.solve(auction).await, - Solver::Naive(solver) => solver.solve(auction).await, - }; - metrics::solved(&deadline, &solutions); - solutions - } -} diff --git a/crates/solvers/src/domain/solver/naive.rs b/crates/solvers/src/domain/solver/naive.rs deleted file mode 100644 index 8b31ff7227..0000000000 --- a/crates/solvers/src/domain/solver/naive.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! "Naive" solver implementation. -//! -//! The naive solver is a solver that collects all orders over a single token -//! pair, computing how many leftover tokens can't be matched peer-to-peer, and -//! matching that excess over a Uniswap V2 pool. This allows for naive -//! coincidence of wants over a single Uniswap V2 pools. - -use { - crate::{ - boundary, - domain::{auction, eth, liquidity, order, solution}, - }, - std::collections::HashMap, -}; - -pub struct Naive; - -impl Naive { - /// Solves the specified auction, returning a vector of all possible - /// solutions. - pub async fn solve(&self, auction: auction::Auction) -> Vec { - // Make sure to push the CPU-heavy code to a separate thread in order to - // not lock up the [`tokio`] runtime and cause it to slow down handling - // the real async things. - let span = tracing::Span::current(); - tokio::task::spawn_blocking(move || { - let _entered = span.enter(); - let groups = group_by_token_pair(&auction); - groups - .values() - .enumerate() - .filter_map(|(i, group)| { - boundary::naive::solve(&group.orders, group.liquidity).map(|solution| { - let gas = solution::INITIALIZATION_COST - + solution::SETTLEMENT - + solution::ERC20_TRANSFER * solution.trades.len() as u64 * 2 - + group.liquidity.gas.0.as_u64(); // this is pessimistic in case the pool is not used - solution - .with_gas(eth::Gas(gas.into())) - .with_id(solution::Id(i as u64)) - }) - }) - .map(|solution| solution.with_buffers_internalizations(&auction.tokens)) - .collect() - }) - .await - .expect("naive solver unexpected panic") - } -} - -#[derive(Debug)] -struct Group<'a> { - orders: Vec<&'a order::Order>, - liquidity: &'a liquidity::Liquidity, - pool: &'a liquidity::constant_product::Pool, -} - -type Groups<'a> = HashMap>; - -/// Groups an auction by token pairs, where each group contains all orders over -/// the token pair as well as the **deepest** constant product pool (i.e. most -/// liquidity, which translates to a higher `K` value for Uniswap V2 style -/// constant product pools). -fn group_by_token_pair(auction: &auction::Auction) -> Groups { - let mut groups = Groups::new(); - - for liquidity in &auction.liquidity { - let pool = match &liquidity.state { - liquidity::State::ConstantProduct(pool) => pool, - _ => continue, - }; - - groups - .entry(pool.tokens()) - .and_modify(|group| { - if group.pool.k() < pool.k() { - group.liquidity = liquidity; - group.pool = pool; - } - }) - .or_insert_with(|| Group { - orders: Vec::new(), - liquidity, - pool, - }); - } - - for order in &auction.orders { - // The naive solver algorithm is sensitive to 0-amount orders (i.e. they - // cause panics). Make sure we don't consider them. - if order.sell.amount.is_zero() || order.buy.amount.is_zero() { - continue; - } - - let tokens = match liquidity::TokenPair::new(order.sell.token, order.buy.token) { - Some(value) => value, - None => continue, - }; - - groups - .entry(tokens) - .and_modify(|group| group.orders.push(order)); - } - - groups.retain(|_, group| !group.orders.is_empty()); - groups -} diff --git a/crates/solvers/src/infra/cli.rs b/crates/solvers/src/infra/cli.rs index 3130bbd9cd..002ba141da 100644 --- a/crates/solvers/src/infra/cli.rs +++ b/crates/solvers/src/infra/cli.rs @@ -35,6 +35,4 @@ pub enum Command { #[clap(long, env)] config: PathBuf, }, - /// optimistically batch similar orders and get difference from AMMs - Naive, } diff --git a/crates/solvers/src/infra/config/baseline.rs b/crates/solvers/src/infra/config.rs similarity index 81% rename from crates/solvers/src/infra/config/baseline.rs rename to crates/solvers/src/infra/config.rs index 354d383bff..9f17f40c66 100644 --- a/crates/solvers/src/infra/config/baseline.rs +++ b/crates/solvers/src/infra/config.rs @@ -1,7 +1,7 @@ use { crate::{ - domain::{eth, solver::baseline}, - infra::{config::unwrap_or_log, contracts}, + domain::{eth, solver}, + infra::contracts, util::serialize, }, chain::Chain, @@ -9,7 +9,7 @@ use { serde::Deserialize, serde_with::serde_as, shared::price_estimation::gas::SETTLEMENT_OVERHEAD, - std::path::Path, + std::{fmt::Debug, path::Path}, tokio::fs, }; @@ -55,7 +55,7 @@ struct Config { /// # Panics /// /// This method panics if the config is invalid or on I/O errors. -pub async fn load(path: &Path) -> baseline::Config { +pub async fn load(path: &Path) -> solver::Config { let data = fs::read_to_string(path) .await .unwrap_or_else(|e| panic!("I/O error while reading {path:?}: {e:?}")); @@ -73,7 +73,7 @@ pub async fn load(path: &Path) -> baseline::Config { ), }; - baseline::Config { + solver::Config { weth, base_tokens: config .base_tokens @@ -87,6 +87,24 @@ pub async fn load(path: &Path) -> baseline::Config { } } +/// Unwraps result or logs a `TOML` parsing error. +fn unwrap_or_log(result: Result, path: &P) -> T +where + E: Debug, + P: Debug, +{ + result.unwrap_or_else(|err| { + if std::env::var("TOML_TRACE_ERROR").is_ok_and(|v| v == "1") { + panic!("failed to parse TOML config at {path:?}: {err:#?}") + } else { + panic!( + "failed to parse TOML config at: {path:?}. Set TOML_TRACE_ERROR=1 to print \ + parsing error but this may leak secrets." + ) + } + }) +} + /// Returns minimum gas used for settling a single order. /// (not accounting for the cost of additional interactions) fn default_gas_offset() -> i64 { diff --git a/crates/solvers/src/infra/config/mod.rs b/crates/solvers/src/infra/config/mod.rs deleted file mode 100644 index 0804e5834a..0000000000 --- a/crates/solvers/src/infra/config/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::fmt::Debug; - -pub mod baseline; - -/// Unwraps result or logs a `TOML` parsing error. -fn unwrap_or_log(result: Result, path: &P) -> T -where - E: Debug, - P: Debug, -{ - result.unwrap_or_else(|err| { - if std::env::var("TOML_TRACE_ERROR").is_ok_and(|v| v == "1") { - panic!("failed to parse TOML config at {path:?}: {err:#?}") - } else { - panic!( - "failed to parse TOML config at: {path:?}. Set TOML_TRACE_ERROR=1 to print \ - parsing error but this may leak secrets." - ) - } - }) -} diff --git a/crates/solvers/src/run.rs b/crates/solvers/src/run.rs index e047643453..1b2e212db9 100644 --- a/crates/solvers/src/run.rs +++ b/crates/solvers/src/run.rs @@ -2,7 +2,7 @@ use tokio::signal::unix::{self, SignalKind}; use { crate::{ - domain::solver::{self, Solver}, + domain::solver, infra::{cli, config}, }, clap::Parser, @@ -30,10 +30,9 @@ async fn run_with(args: cli::Args, bind: Option>) { let solver = match args.command { cli::Command::Baseline { config } => { - let config = config::baseline::load(&config).await; - Solver::Baseline(solver::Baseline::new(config)) + let config = config::load(&config).await; + solver::Solver::new(config) } - cli::Command::Naive => Solver::Naive(solver::Naive), }; crate::api::Api { diff --git a/crates/solvers/src/tests/baseline/bal_liquidity.rs b/crates/solvers/src/tests/cases/bal_liquidity.rs similarity index 100% rename from crates/solvers/src/tests/baseline/bal_liquidity.rs rename to crates/solvers/src/tests/cases/bal_liquidity.rs diff --git a/crates/solvers/src/tests/baseline/buy_order_rounding.rs b/crates/solvers/src/tests/cases/buy_order_rounding.rs similarity index 100% rename from crates/solvers/src/tests/baseline/buy_order_rounding.rs rename to crates/solvers/src/tests/cases/buy_order_rounding.rs diff --git a/crates/solvers/src/tests/baseline/direct_swap.rs b/crates/solvers/src/tests/cases/direct_swap.rs similarity index 100% rename from crates/solvers/src/tests/baseline/direct_swap.rs rename to crates/solvers/src/tests/cases/direct_swap.rs diff --git a/crates/solvers/src/tests/baseline/internalization.rs b/crates/solvers/src/tests/cases/internalization.rs similarity index 100% rename from crates/solvers/src/tests/baseline/internalization.rs rename to crates/solvers/src/tests/cases/internalization.rs diff --git a/crates/solvers/src/tests/baseline/limit_order_quoting.rs b/crates/solvers/src/tests/cases/limit_order_quoting.rs similarity index 100% rename from crates/solvers/src/tests/baseline/limit_order_quoting.rs rename to crates/solvers/src/tests/cases/limit_order_quoting.rs diff --git a/crates/solvers/src/tests/baseline/mod.rs b/crates/solvers/src/tests/cases/mod.rs similarity index 100% rename from crates/solvers/src/tests/baseline/mod.rs rename to crates/solvers/src/tests/cases/mod.rs diff --git a/crates/solvers/src/tests/baseline/partial_fill.rs b/crates/solvers/src/tests/cases/partial_fill.rs similarity index 100% rename from crates/solvers/src/tests/baseline/partial_fill.rs rename to crates/solvers/src/tests/cases/partial_fill.rs diff --git a/crates/solvers/src/tests/mod.rs b/crates/solvers/src/tests/mod.rs index f12dc63a5a..fcf64af0e0 100644 --- a/crates/solvers/src/tests/mod.rs +++ b/crates/solvers/src/tests/mod.rs @@ -10,8 +10,7 @@ use { tokio::{sync::oneshot, task::JoinHandle}, }; -mod baseline; -mod naive; +mod cases; /// A solver engine handle for E2E testing. pub struct SolverEngine { diff --git a/crates/solvers/src/tests/naive/extract_deepest_pool.rs b/crates/solvers/src/tests/naive/extract_deepest_pool.rs deleted file mode 100644 index 2f046cb6ba..0000000000 --- a/crates/solvers/src/tests/naive/extract_deepest_pool.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! Test that demonstrates that the Naive solver will use the **deepest** pool -//! for solving when multiple different UniswapV2-like pools exist for a -//! specific token pair. -//! -//! The rationale behind this choise is that the deepest pools are typically the -//! most representative of the actual token price, and in general give better -//! prices for larger orders (in theory, for smaller orders it is possible for -//! the shallow pool to offer better prices, but this should be in exceptional -//! cases, and not worth considering in the solver in order to keep things -//! simple). - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0x0101010101010101010101010101010101010101", - "buyToken": "0x0202020202020202020202020202020202020202", - "sellAmount": "100", - "fullSellAmount": "1000000000000000000", - "buyAmount": "1", - "fullBuyAmount": "1000000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x0101010101010101010101010101010101010101": { - "balance": "100" - }, - "0x0202020202020202020202020202020202020202": { - "balance": "100" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x2222222222222222222222222222222222222222", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "0" - }, - { - "kind": "constantProduct", - "tokens": { - "0x0101010101010101010101010101010101010101": { - "balance": "10000000" - }, - "0x0202020202020202020202020202020202020202": { - "balance": "10000000" - } - }, - "fee": "0.003", - "id": "1", - "address": "0x1111111111111111111111111111111111111111", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "0" - }, - { - "kind": "constantProduct", - "tokens": { - "0x0303030303030303030303030303030303030303": { - "balance": "10000000000000000" - }, - "0x0404040404040404040404040404040404040404": { - "balance": "10000000000000000" - } - }, - "fee": "0.003", - "id": "2", - "address": "0x3333333333333333333333333333333333333333", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "0" - }, - ], - "effectiveGasPrice": "0", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x0101010101010101010101010101010101010101": "99", - "0x0202020202020202020202020202020202020202": "100", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "100", - }, - ], - "preInteractions": [], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "1", - "inputToken": "0x0101010101010101010101010101010101010101", - "outputToken": "0x0202020202020202020202020202020202020202", - "inputAmount": "100", - "outputAmount": "99" - }, - ], - "postInteractions": [], - "gas": 94391, - }] - }), - ); -} diff --git a/crates/solvers/src/tests/naive/filters_out_of_price_orders.rs b/crates/solvers/src/tests/naive/filters_out_of_price_orders.rs deleted file mode 100644 index 8ea6f874e3..0000000000 --- a/crates/solvers/src/tests/naive/filters_out_of_price_orders.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! This test verifies that orders that are out of price get filtered out, but -//! a solution with the "reasonably" priced orders is produced. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn sell_orders_on_both_sides() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - // Unreasonable order a -> b - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "1000000000000000000", - "fullSellAmount": "1000000000000000000", - "buyAmount": "1000000000000000000000", - "fullBuyAmount": "1000000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - // Reasonable order a -> b - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "1000000000000000000000", - "fullSellAmount": "1000000000000000000000", - "buyAmount": "1000000000000000000000", - "fullBuyAmount": "1000000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - // Reasonable order a -> b - { - "uid": "0x0303030303030303030303030303030303030303030303030303030303030303\ - 0303030303030303030303030303030303030303\ - 03030303", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "1000000000000000000000", - "fullSellAmount": "1000000000000000000000", - "buyAmount": "1000000000000000000000", - "fullBuyAmount": "1000000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - // Unreasonable order a -> b - { - "uid": "0x0404040404040404040404040404040404040404040404040404040404040404\ - 0404040404040404040404040404040404040404\ - 04040404", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "2000000000000000000", - "fullSellAmount": "2000000000000000000", - "buyAmount": "1000000000000000000000", - "fullBuyAmount": "1000000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "1000000000000000000000000", - "0x000000000000000000000000000000000000000b": "1000000000000000000000000", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0303030303030303030303030303030303030303030303030303030303030303\ - 0303030303030303030303030303030303030303\ - 03030303", - "executedAmount": "1000000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "1000000000000000000000", - }, - ], - "preInteractions": [], - "interactions": [], - "postInteractions": [], - "gas": 259417, - }] - }), - ); -} diff --git a/crates/solvers/src/tests/naive/limit_order_price.rs b/crates/solvers/src/tests/naive/limit_order_price.rs deleted file mode 100644 index 0ac2df06ab..0000000000 --- a/crates/solvers/src/tests/naive/limit_order_price.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! This test verifies that the limit order's limit price is respected after -//! surplus fees are taken from the order. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "buyToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "sellAmount": "22397494", - "fullSellAmount": "1000000000000000000", - "buyAmount": "18477932550000000", - "fullBuyAmount": "1000000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "limit", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "balance": "36338096110368" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "30072348537379906026018" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x0000000000000000000000000000000000000000", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [] - }), - ); -} diff --git a/crates/solvers/src/tests/naive/matches_orders.rs b/crates/solvers/src/tests/naive/matches_orders.rs deleted file mode 100644 index 031224f057..0000000000 --- a/crates/solvers/src/tests/naive/matches_orders.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! Tests for various permutiations of matching combinatinos of sell and buy -//! orders. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn sell_orders_on_both_sides() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "40000000000000000000", - "fullSellAmount": "40000000000000000000", - "buyAmount": "30000000000000000000", - "fullBuyAmount": "30000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "100000000000000000000", - "fullSellAmount": "100000000000000000000", - "buyAmount": "90000000000000000000", - "fullBuyAmount": "90000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "57576575881490625723", - "0x000000000000000000000000000000000000000b": "54287532963535509684", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "40000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "100000000000000000000", - }, - ], - "preInteractions": [], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000b", - "outputToken": "0x000000000000000000000000000000000000000a", - "inputAmount": "57576575881490625723", - "outputAmount": "54287532963535509685" - }, - ], - "postInteractions": [], - "gas": 259417, - }] - }), - ); -} - -#[tokio::test] -async fn sell_orders_on_one_side() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "40000000000000000000", - "fullSellAmount": "40000000000000000000", - "buyAmount": "30000000000000000000", - "fullBuyAmount": "30000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "100000000000000000000", - "fullSellAmount": "100000000000000000000", - "buyAmount": "90000000000000000000", - "fullBuyAmount": "90000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "139560520142598496101", - "0x000000000000000000000000000000000000000b": "140000000000000000000", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "40000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "100000000000000000000", - }, - ], - "preInteractions": [], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000a", - "outputToken": "0x000000000000000000000000000000000000000b", - "inputAmount": "140000000000000000000", - "outputAmount": "139560520142598496102" - }, - ], - "postInteractions": [], - "gas": 259417, - }] - }), - ); -} - -#[tokio::test] -async fn buy_orders_on_both_sides() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "40000000000000000000", - "fullSellAmount": "40000000000000000000", - "buyAmount": "30000000000000000000", - "fullBuyAmount": "30000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "buy", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "100000000000000000000", - "fullSellAmount": "100000000000000000000", - "buyAmount": "90000000000000000000", - "fullBuyAmount": "90000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "buy", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "66231662019024105282", - "0x000000000000000000000000000000000000000b": "61942706346833798925", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "30000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "90000000000000000000", - }, - ], - "preInteractions": [], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000b", - "outputToken": "0x000000000000000000000000000000000000000a", - "inputAmount": "66231662019024105282", - "outputAmount": "61942706346833798926" - }, - ], - "postInteractions": [], - "gas": 259417, - }] - }), - ); -} - -#[tokio::test] -async fn buy_and_sell_orders() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "40000000000000000000", - "fullSellAmount": "40000000000000000000", - "buyAmount": "30000000000000000000", - "fullBuyAmount": "30000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "buy", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "100000000000000000000", - "fullSellAmount": "40000000000000000000", - "buyAmount": "90000000000000000000", - "fullBuyAmount": "30000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "70000000000000000000", - "0x000000000000000000000000000000000000000b": "65237102608923246618", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "30000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "100000000000000000000", - }, - ], - "preInteractions": [], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000b", - "outputToken": "0x000000000000000000000000000000000000000a", - "inputAmount": "70000000000000000000", - "outputAmount": "65237102608923246619" - }, - ], - "postInteractions": [], - "gas": 259417, - }] - }), - ); -} diff --git a/crates/solvers/src/tests/naive/mod.rs b/crates/solvers/src/tests/naive/mod.rs deleted file mode 100644 index df3005f3f3..0000000000 --- a/crates/solvers/src/tests/naive/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod extract_deepest_pool; -mod filters_out_of_price_orders; -mod limit_order_price; -mod matches_orders; -mod reserves_too_small; -mod rounds_prices_in_favour_of_traders; -mod swap_less_than_reserves; -mod without_pool; diff --git a/crates/solvers/src/tests/naive/reserves_too_small.rs b/crates/solvers/src/tests/naive/reserves_too_small.rs deleted file mode 100644 index a4c72d5132..0000000000 --- a/crates/solvers/src/tests/naive/reserves_too_small.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! This test verifies that it filters out large orders when pool reserves are -//! too small to be able to handle them. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "70145218378783248142575", - "fullSellAmount": "70145218378783248142575", - "buyAmount": "70123226323", - "fullBuyAmount": "70123226323", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "900000000000000", - "fullSellAmount": "900000000000000", - "buyAmount": "100", - "fullBuyAmount": "100", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "25000075" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "2500007500" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "2500007430", - "0x000000000000000000000000000000000000000b": "900000000000000", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "900000000000000", - }, - ], - "preInteractions": [], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000a", - "outputToken": "0x000000000000000000000000000000000000000b", - "inputAmount": "900000000000000", - "outputAmount": "2500007430" - }, - ], - "postInteractions": [], - "gas": 204391, - }] - }), - ); -} diff --git a/crates/solvers/src/tests/naive/rounds_prices_in_favour_of_traders.rs b/crates/solvers/src/tests/naive/rounds_prices_in_favour_of_traders.rs deleted file mode 100644 index 1be1dd491c..0000000000 --- a/crates/solvers/src/tests/naive/rounds_prices_in_favour_of_traders.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! This test verifies that rounding is done in favour of the traders. This is -//! a weird detail that stems from the fact the UniswapV2-like pool swaps are -//! encoded as **swapTokensForExactTokens** (i.e. a "buy" order). The reason for -//! this is to make settlements less likely to revert (because buy swaps have -//! order fees as guaranteed "buffers", while sell swaps only have buffers if -//! they are already in the contract). The rounding is needed because the -//! settlement contract will round executed amounts in favour or the trader, -//! meaning that the clearing prices can cause the total buy amount to be a few -//! wei larger than the exact output that is encoded. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "9000000", - "fullSellAmount": "9000000", - "buyAmount": "8500000", - "fullBuyAmount": "8500000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "8500000", - "fullSellAmount": "8500000", - "buyAmount": "8000001", - "fullBuyAmount": "8000001", - "feePolicies": [], - "validTo": 0, - "kind": "buy", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "996999", - "0x000000000000000000000000000000000000000b": "999999", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "9000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "8000001", - }, - ], - "preInteractions": [], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000a", - "outputToken": "0x000000000000000000000000000000000000000b", - "inputAmount": "999999", - "outputAmount": "997000" - }, - ], - "postInteractions": [], - "gas": 259417, - }] - }), - ); -} diff --git a/crates/solvers/src/tests/naive/swap_less_than_reserves.rs b/crates/solvers/src/tests/naive/swap_less_than_reserves.rs deleted file mode 100644 index 3df8a591f0..0000000000 --- a/crates/solvers/src/tests/naive/swap_less_than_reserves.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! This test verifies that given a swap with multiple orders, including limit -//! orders, the settlement building does not cause the naive solver to generate -//! a solution that swaps more than the pools reserves. -//! -//! This test verifies a regression that was introduced with limit orders, where -//! the incorrect order amounts were used for computing the final pool swap -//! amounts, causing it buy more from the pool than it actual had. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0xD533a949740bb3306d119CC777fa900bA034cd52", - "buyToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "sellAmount": "2161740107040163317224", - "fullSellAmount": "2161740107040163317224", - "buyAmount": "2146544862", - "fullBuyAmount": "2146544862", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "limit", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "buyToken": "0xD533a949740bb3306d119CC777fa900bA034cd52", - "sellAmount": "495165988", - "fullSellAmount": "495165988", - "buyAmount": "1428571428571428571428", - "fullBuyAmount": "1428571428571428571428", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "limit", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { - "balance": "32275540" - }, - "0xD533a949740bb3306d119CC777fa900bA034cd52": { - "balance": "33308141034569852391" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x210a97ba874a8e279c95b350ae8ba143a143c159", - "router": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [] - }), - ); -} diff --git a/crates/solvers/src/tests/naive/without_pool.rs b/crates/solvers/src/tests/naive/without_pool.rs deleted file mode 100644 index d6b4419092..0000000000 --- a/crates/solvers/src/tests/naive/without_pool.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! This test verifies that the naive solver doesn't use liquidity from the pool -//! when order amounts overlap. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new("naive", tests::Config::None).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "1001000000000000000000", - "fullSellAmount": "1001000000000000000000", - "buyAmount": "1000000000000000000000", - "fullBuyAmount": "1000000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "1001000000000000000000", - "fullSellAmount": "1001000000000000000000", - "buyAmount": "1000000000000000000000", - "fullBuyAmount": "1000000000000000000000", - "feePolicies": [], - "validTo": 0, - "kind": "sell", - "owner": "0x5b1e2c2762667331bc91648052f646d1b0d35984", - "partiallyFillable": false, - "preInteractions": [], - "postInteractions": [], - "sellTokenSource": "erc20", - "buyTokenDestination": "erc20", - "class": "market", - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "signingScheme": "presign", - "signature": "0x", - }, - ], - "liquidity": [ - { - "kind": "constantProduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000001000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "router": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - "surplusCapturingJitOrderOwners": [] - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "1000000000000000000000000", - "0x000000000000000000000000000000000000000b": "1000001000000000000000000", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "1001000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "1001000000000000000000", - }, - ], - "preInteractions": [], - "interactions": [], - "postInteractions": [], - "gas": 259417, - }] - }), - ); -} From a520f40182e085fd222f063610f01c0e1a15a81b Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Fri, 13 Dec 2024 13:52:23 +0100 Subject: [PATCH 15/25] Verify quote with pre interactions (#3160) # Description Not too long ago https://github.com/cowprotocol/services/pull/3081 changed the simulation logic to not do any set up steps on behalf of the user if the quote provided some pre-interactions. That was done to avoid reverts when a pre-interactions actually set up everything for the trade (e.g. get balances by unstaking). However, this caused issues with verifying quotes with `permit` pre-interactions. Because the quote provided the `permit` pre-interaction the simulation logic also did not try to fake balances although it could have. # Changes Now we again always call the code to setup approvals and such BUT we do it as the very last `pre-interaction`. That way this operation becomes a no-op if the user already sets up everything with their pre-interactions OR does the usual logic and reverts with helpful error messages if the pre-conditions can't be met. Since this function may now be a no-op I renamed it to `ensureSwapPreconditions()`. Additionally this moves code that was previously run before the settlement inside the settlement which increases the measured gas cost. To work around that I also introduced a wrapper function in `Solver.sol` that simply calls the `Trader` function and discounts the gas costs. Note that we can't simply do the necessary steps in the `Solver` because we need to be inside the context of the `Trader` to be able to set approvals and such. That way we can verify quotes which do no, partial or the entire swap setup in their pre-interactions while keeping good error messages and accurate gas estimates. Kudos to @nlordell for the idea. ## How to test I added an e2e test that fails without the PR. --- crates/contracts/artifacts/Solver.json | 2 +- crates/contracts/artifacts/Trader.json | 2 +- crates/contracts/solidity/Solver.sol | 57 ++++++-------- crates/contracts/solidity/Trader.sol | 11 ++- crates/e2e/tests/e2e/quote_verification.rs | 35 +++++++++ .../src/price_estimation/trade_verifier.rs | 77 +++++++++++++------ 6 files changed, 123 insertions(+), 61 deletions(-) diff --git a/crates/contracts/artifacts/Solver.json b/crates/contracts/artifacts/Solver.json index 19639cbd96..a046d11cdf 100644 --- a/crates/contracts/artifacts/Solver.json +++ b/crates/contracts/artifacts/Solver.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"components":[{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"address","name":"spardose","type":"address"}],"internalType":"struct Solver.Mock","name":"mock","type":"tuple"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506109e0806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806317b86b161461003b5780633bbb2e1d14610065575b600080fd5b61004e61004936600461072c565b61007a565b60405161005c929190610817565b60405180910390f35b610078610073366004610865565b6102aa565b005b60006060333014610111576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b8251156101bf5760208301516040517f66b00f6800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8f811660048301528d81166024830152604482018d90528b811660648301529182166084820152908d16906366b00f689060a401600060405180830381600087803b1580156101a657600080fd5b505af11580156101ba573d6000803e3d6000fd5b505050505b60008673ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461021a576040519150601f19603f3d011682016040523d82523d6000602084013e61021f565b606091505b505090505061022f88888f6103df565b61023a8d86866104b7565b915061024788888f6103df565b600180548060200260200160405190810160405280929190818152602001828054801561029357602002820191906000526020600020905b81548152602001906001019080831161027f575b505050505090509b509b9950505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610377576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561034e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037291906108ac565b610390565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156103d9575a6103b790826108f4565b6103c39061116c61090d565b6000808282546103d3919061090d565b90915550505b50505050565b60005b828110156103d95730633bbb2e1d85858481811061040257610402610920565b9050602002016020810190610417919061094f565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561048c57600080fd5b505af11580156104a0573d6000803e3d6000fd5b5050505080806104af9061096c565b9150506103e2565b6000805a905061051484848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610536565b506000545a61052390836108f4565b61052d91906108f4565b95945050505050565b60606105448360008461054b565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161057591906109a4565b60006040518083038185875af1925050503d80600081146105b2576040519150601f19603f3d011682016040523d82523d6000602084013e6105b7565b606091505b5092509050806105c957815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105f357600080fd5b50565b8035610601816105d1565b919050565b60008083601f84011261061857600080fd5b50813567ffffffffffffffff81111561063057600080fd5b6020830191508360208260051b850101111561064b57600080fd5b9250929050565b60008083601f84011261066457600080fd5b50813567ffffffffffffffff81111561067c57600080fd5b60208301915083602082850101111561064b57600080fd5b8035801515811461060157600080fd5b6000604082840312156106b657600080fd5b6040516040810181811067ffffffffffffffff82111715610700577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405290508061070f83610694565b8152602083013561071f816105d1565b6020919091015292915050565b60008060008060008060008060008060006101408c8e03121561074e57600080fd5b6107588c356105d1565b8b359a5061076960208d01356105d1565b60208c0135995061077c60408d016105f6565b985060608c0135975061079160808d016105f6565b965067ffffffffffffffff8060a08e013511156107ad57600080fd5b6107bd8e60a08f01358f01610606565b90975095506107ce60c08e016105f6565b94508060e08e013511156107e157600080fd5b506107f28d60e08e01358e01610652565b90935091506108058d6101008e016106a4565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156108585784518352938301939183019160010161083c565b5090979650505050505050565b60008060006060848603121561087a57600080fd5b8335610885816105d1565b92506020840135610895816105d1565b91506108a360408501610694565b90509250925092565b6000602082840312156108be57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610907576109076108c5565b92915050565b80820180821115610907576109076108c5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561096157600080fd5b8135610544816105d1565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361099d5761099d6108c5565b5060010190565b6000825160005b818110156109c557602081860181015185830152016109ab565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317b86b161461003b5780633bbb2e1d14610065575b600080fd5b61004e61004936600461072c565b61007a565b60405161005c929190610817565b60405180910390f35b610078610073366004610865565b6102aa565b005b60006060333014610111576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b8251156101bf5760208301516040517f66b00f6800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8f811660048301528d81166024830152604482018d90528b811660648301529182166084820152908d16906366b00f689060a401600060405180830381600087803b1580156101a657600080fd5b505af11580156101ba573d6000803e3d6000fd5b505050505b60008673ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461021a576040519150601f19603f3d011682016040523d82523d6000602084013e61021f565b606091505b505090505061022f88888f6103df565b61023a8d86866104b7565b915061024788888f6103df565b600180548060200260200160405190810160405280929190818152602001828054801561029357602002820191906000526020600020905b81548152602001906001019080831161027f575b505050505090509b509b9950505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610377576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561034e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037291906108ac565b610390565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156103d9575a6103b790826108f4565b6103c39061116c61090d565b6000808282546103d3919061090d565b90915550505b50505050565b60005b828110156103d95730633bbb2e1d85858481811061040257610402610920565b9050602002016020810190610417919061094f565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561048c57600080fd5b505af11580156104a0573d6000803e3d6000fd5b5050505080806104af9061096c565b9150506103e2565b6000805a905061051484848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610536565b506000545a61052390836108f4565b61052d91906108f4565b95945050505050565b60606105448360008461054b565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161057591906109a4565b60006040518083038185875af1925050503d80600081146105b2576040519150601f19603f3d011682016040523d82523d6000602084013e6105b7565b606091505b5092509050806105c957815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105f357600080fd5b50565b8035610601816105d1565b919050565b60008083601f84011261061857600080fd5b50813567ffffffffffffffff81111561063057600080fd5b6020830191508360208260051b850101111561064b57600080fd5b9250929050565b60008083601f84011261066457600080fd5b50813567ffffffffffffffff81111561067c57600080fd5b60208301915083602082850101111561064b57600080fd5b8035801515811461060157600080fd5b6000604082840312156106b657600080fd5b6040516040810181811067ffffffffffffffff82111715610700577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405290508061070f83610694565b8152602083013561071f816105d1565b6020919091015292915050565b60008060008060008060008060008060006101408c8e03121561074e57600080fd5b6107588c356105d1565b8b359a5061076960208d01356105d1565b60208c0135995061077c60408d016105f6565b985060608c0135975061079160808d016105f6565b965067ffffffffffffffff8060a08e013511156107ad57600080fd5b6107bd8e60a08f01358f01610606565b90975095506107ce60c08e016105f6565b94508060e08e013511156107e157600080fd5b506107f28d60e08e01358e01610652565b90935091506108058d6101008e016106a4565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156108585784518352938301939183019160010161083c565b5090979650505050505050565b60008060006060848603121561087a57600080fd5b8335610885816105d1565b92506020840135610895816105d1565b91506108a360408501610694565b90509250925092565b6000602082840312156108be57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610907576109076108c5565b92915050565b80820180821115610907576109076108c5565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561096157600080fd5b8135610544816105d1565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361099d5761099d6108c5565b5060010190565b6000825160005b818110156109c557602081860181015185830152016109ab565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"inputs":[{"internalType":"contract Trader","name":"trader","type":"address"},{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address","name":"spardose","type":"address"}],"name":"ensureTradePreconditions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b506109a3806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80631d47e7f4146100465780632582edb4146100705780633bbb2e1d14610085575b600080fd5b610059610054366004610696565b610098565b604051610067929190610758565b60405180910390f35b61008361007e3660046107a6565b610215565b005b61008361009336600461081f565b6102f1565b6000606033301461012f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b60008573ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461018a576040519150601f19603f3d011682016040523d82523d6000602084013e61018f565b606091505b505090505061019f87878a610426565b6101aa8885856104fe565b91506101b787878a610426565b600180548060200260200160405190810160405280929190818152602001828054801561020357602002820191906000526020600020905b8154815260200190600101908083116101ef575b50505050509050965096945050505050565b60005a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a401600060405180830381600087803b1580156102a457600080fd5b505af11580156102b8573d6000803e3d6000fd5b505050505a6102c7908261089e565b6102d39061116c6108b7565b6000808282546102e391906108b7565b909155505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103be576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa158015610395573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103b991906108ca565b6103d7565b8373ffffffffffffffffffffffffffffffffffffffff16315b815460018101835560009283526020909220909101558115610420575a6103fe908261089e565b61040a9061116c6108b7565b60008082825461041a91906108b7565b90915550505b50505050565b60005b828110156104205730633bbb2e1d858584818110610449576104496108e3565b905060200201602081019061045e9190610912565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156104d357600080fd5b505af11580156104e7573d6000803e3d6000fd5b5050505080806104f69061092f565b915050610429565b6000805a905061055b84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff89169291505061057d565b506000545a61056a908361089e565b610574919061089e565b95945050505050565b606061058b83600084610592565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516105bc9190610967565b60006040518083038185875af1925050503d80600081146105f9576040519150601f19603f3d011682016040523d82523d6000602084013e6105fe565b606091505b50925090508061061057815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461063a57600080fd5b50565b803561064881610618565b919050565b60008083601f84011261065f57600080fd5b50813567ffffffffffffffff81111561067757600080fd5b60208301915083602082850101111561068f57600080fd5b9250929050565b600080600080600080608087890312156106af57600080fd5b86356106ba81610618565b9550602087013567ffffffffffffffff808211156106d757600080fd5b818901915089601f8301126106eb57600080fd5b8135818111156106fa57600080fd5b8a60208260051b850101111561070f57600080fd5b602083019750955061072360408a0161063d565b9450606089013591508082111561073957600080fd5b5061074689828a0161064d565b979a9699509497509295939492505050565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156107995784518352938301939183019160010161077d565b5090979650505050505050565b60008060008060008060c087890312156107bf57600080fd5b86356107ca81610618565b955060208701356107da81610618565b945060408701356107ea81610618565b935060608701359250608087013561080181610618565b915060a087013561081181610618565b809150509295509295509295565b60008060006060848603121561083457600080fd5b833561083f81610618565b9250602084013561084f81610618565b91506040840135801515811461086457600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156108b1576108b161086f565b92915050565b808201808211156108b1576108b161086f565b6000602082840312156108dc57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561092457600080fd5b813561058b81610618565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109605761096061086f565b5060010190565b6000825160005b81811015610988576020818601810151858301520161096e565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100415760003560e01c80631d47e7f4146100465780632582edb4146100705780633bbb2e1d14610085575b600080fd5b610059610054366004610696565b610098565b604051610067929190610758565b60405180910390f35b61008361007e3660046107a6565b610215565b005b61008361009336600461081f565b6102f1565b6000606033301461012f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b60008573ffffffffffffffffffffffffffffffffffffffff16600060405160006040518083038185875af1925050503d806000811461018a576040519150601f19603f3d011682016040523d82523d6000602084013e61018f565b606091505b505090505061019f87878a610426565b6101aa8885856104fe565b91506101b787878a610426565b600180548060200260200160405190810160405280929190818152602001828054801561020357602002820191906000526020600020905b8154815260200190600101908083116101ef575b50505050509050965096945050505050565b60005a6040517f542eb77d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152878116602483015260448201879052858116606483015284811660848301529192509088169063542eb77d9060a401600060405180830381600087803b1580156102a457600080fd5b505af11580156102b8573d6000803e3d6000fd5b505050505a6102c7908261089e565b6102d39061116c6108b7565b6000808282546102e391906108b7565b909155505050505050505050565b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff8616146103be576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa158015610395573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103b991906108ca565b6103d7565b8373ffffffffffffffffffffffffffffffffffffffff16315b815460018101835560009283526020909220909101558115610420575a6103fe908261089e565b61040a9061116c6108b7565b60008082825461041a91906108b7565b90915550505b50505050565b60005b828110156104205730633bbb2e1d858584818110610449576104496108e3565b905060200201602081019061045e9190610912565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156104d357600080fd5b505af11580156104e7573d6000803e3d6000fd5b5050505080806104f69061092f565b915050610429565b6000805a905061055b84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff89169291505061057d565b506000545a61056a908361089e565b610574919061089e565b95945050505050565b606061058b83600084610592565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516105bc9190610967565b60006040518083038185875af1925050503d80600081146105f9576040519150601f19603f3d011682016040523d82523d6000602084013e6105fe565b606091505b50925090508061061057815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461063a57600080fd5b50565b803561064881610618565b919050565b60008083601f84011261065f57600080fd5b50813567ffffffffffffffff81111561067757600080fd5b60208301915083602082850101111561068f57600080fd5b9250929050565b600080600080600080608087890312156106af57600080fd5b86356106ba81610618565b9550602087013567ffffffffffffffff808211156106d757600080fd5b818901915089601f8301126106eb57600080fd5b8135818111156106fa57600080fd5b8a60208260051b850101111561070f57600080fd5b602083019750955061072360408a0161063d565b9450606089013591508082111561073957600080fd5b5061074689828a0161064d565b979a9699509497509295939492505050565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156107995784518352938301939183019160010161077d565b5090979650505050505050565b60008060008060008060c087890312156107bf57600080fd5b86356107ca81610618565b955060208701356107da81610618565b945060408701356107ea81610618565b935060608701359250608087013561080181610618565b915060a087013561081181610618565b809150509295509295509295565b60008060006060848603121561083457600080fd5b833561083f81610618565b9250602084013561084f81610618565b91506040840135801515811461086457600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156108b1576108b161086f565b92915050565b808201808211156108b1576108b161086f565b6000602082840312156108dc57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60006020828403121561092457600080fd5b813561058b81610618565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109605761096061086f565b5060010190565b6000825160005b81811015610988576020818601810151858301520161096e565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Trader.json b/crates/contracts/artifacts/Trader.json index eef29787b2..2884646dee 100644 --- a/crates/contracts/artifacts/Trader.json +++ b/crates/contracts/artifacts/Trader.json @@ -1 +1 @@ -{"abi":[{"stateMutability":"payable","type":"fallback"},{"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":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address","name":"spardose","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610ba4806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e1461008657806366b00f68146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a13660046109cd565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a6e565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610ad6565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa6107c5565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610b05565b9050838110156103835760006103168286610b1e565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008473ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308873ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104139190610b58565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a79190610b05565b9050838110156105f2576105428673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104ff573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105239190610b58565b73ffffffffffffffffffffffffffffffffffffffff8716906000610800565b6105f28673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610590573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b49190610b58565b73ffffffffffffffffffffffffffffffffffffffff8716907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610800565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa15801561065f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106839190610b05565b9050848110156107bc5773ffffffffffffffffffffffffffffffffffffffff831663494666b6876106b48489610b1e565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b15801561071f57600080fd5b505af1925050508015610730575060015b6107bc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b50505050505050565b6000806107f360017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610b1e565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906108939086168361090b565b905061089e81610920565b610904576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c65640000000000006044820152606401610233565b5050505050565b606061091983600084610947565b9392505050565b60008151600014806109415750818060200190518101906109419190610b75565b92915050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516109719190610ad6565b60006040518083038185875af1925050503d80600081146109ae576040519150601f19603f3d011682016040523d82523d6000602084013e6109b3565b606091505b5092509050806109c557815160208301fd5b509392505050565b6000806000604084860312156109e257600080fd5b83359250602084013567ffffffffffffffff80821115610a0157600080fd5b818601915086601f830112610a1557600080fd5b813581811115610a2457600080fd5b876020828501011115610a3657600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a6b57600080fd5b50565b600080600080600060a08688031215610a8657600080fd5b8535610a9181610a49565b94506020860135610aa181610a49565b9350604086013592506060860135610ab881610a49565b91506080860135610ac881610a49565b809150509295509295909350565b6000825160005b81811015610af75760208186018101518583015201610add565b506000920191825250919050565b600060208284031215610b1757600080fd5b5051919050565b81810381811115610941577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610b6a57600080fd5b815161091981610a49565b600060208284031215610b8757600080fd5b8151801515811461091957600080fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e1461008657806366b00f68146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a13660046109cd565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a6e565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610ad6565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa6107c5565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610b05565b9050838110156103835760006103168286610b1e565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008473ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308873ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104139190610b58565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a79190610b05565b9050838110156105f2576105428673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104ff573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105239190610b58565b73ffffffffffffffffffffffffffffffffffffffff8716906000610800565b6105f28673ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610590573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105b49190610b58565b73ffffffffffffffffffffffffffffffffffffffff8716907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610800565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa15801561065f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106839190610b05565b9050848110156107bc5773ffffffffffffffffffffffffffffffffffffffff831663494666b6876106b48489610b1e565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b15801561071f57600080fd5b505af1925050508015610730575060015b6107bc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b50505050505050565b6000806107f360017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610b1e565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906108939086168361090b565b905061089e81610920565b610904576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c65640000000000006044820152606401610233565b5050505050565b606061091983600084610947565b9392505050565b60008151600014806109415750818060200190518101906109419190610b75565b92915050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516109719190610ad6565b60006040518083038185875af1925050503d80600081146109ae576040519150601f19603f3d011682016040523d82523d6000602084013e6109b3565b606091505b5092509050806109c557815160208301fd5b509392505050565b6000806000604084860312156109e257600080fd5b83359250602084013567ffffffffffffffff80821115610a0157600080fd5b818601915086601f830112610a1557600080fd5b813581811115610a2457600080fd5b876020828501011115610a3657600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a6b57600080fd5b50565b600080600080600060a08688031215610a8657600080fd5b8535610a9181610a49565b94506020860135610aa181610a49565b9350604086013592506060860135610ab881610a49565b91506080860135610ac881610a49565b809150509295509295909350565b6000825160005b81811015610af75760208186018101518583015201610add565b506000920191825250919050565b600060208284031215610b1757600080fd5b5051919050565b81810381811115610941577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610b6a57600080fd5b815161091981610a49565b600060208284031215610b8757600080fd5b8151801515811461091957600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address","name":"spardose","type":"address"}],"name":"ensureTradePreconditions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610cf4806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e14610086578063542eb77d146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610b10565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610bb1565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610c19565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa610ad5565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610c48565b9050838110156103835760006103168286610c61565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008473ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308873ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104139190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a79190610c48565b905083811015610902578473ffffffffffffffffffffffffffffffffffffffff1663095ea7b38773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610518573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053c9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152600060248201526044016020604051808303816000875af19250505080156105e8575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526105e591810190610cc5565b60015b156105ef57505b8473ffffffffffffffffffffffffffffffffffffffff1663095ea7b38773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610656573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061067a9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff90911660048201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60248201526044016020604051808303816000875af1925050508015610745575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261074291810190610cc5565b60015b1561074c57505b60008573ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308973ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107da9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa15801561084a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086e9190610c48565b905084811015610900576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c73000000000000000000000000000000000000000000006064820152608401610233565b505b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa15801561096f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109939190610c48565b905084811015610acc5773ffffffffffffffffffffffffffffffffffffffff831663494666b6876109c48489610c61565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b158015610a2f57600080fd5b505af1925050508015610a40575060015b610acc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b50505050505050565b600080610b0360017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610c61565b8054600190915592915050565b600080600060408486031215610b2557600080fd5b83359250602084013567ffffffffffffffff80821115610b4457600080fd5b818601915086601f830112610b5857600080fd5b813581811115610b6757600080fd5b876020828501011115610b7957600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610bae57600080fd5b50565b600080600080600060a08688031215610bc957600080fd5b8535610bd481610b8c565b94506020860135610be481610b8c565b9350604086013592506060860135610bfb81610b8c565b91506080860135610c0b81610b8c565b809150509295509295909350565b6000825160005b81811015610c3a5760208186018101518583015201610c20565b506000920191825250919050565b600060208284031215610c5a57600080fd5b5051919050565b81810381811115610c9b577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610cb357600080fd5b8151610cbe81610b8c565b9392505050565b600060208284031215610cd757600080fd5b81518015158114610cbe57600080fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e14610086578063542eb77d146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610b10565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610bb1565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610c19565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa610ad5565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610c48565b9050838110156103835760006103168286610c61565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008473ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308873ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104139190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a79190610c48565b905083811015610902578473ffffffffffffffffffffffffffffffffffffffff1663095ea7b38773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610518573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053c9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152600060248201526044016020604051808303816000875af19250505080156105e8575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526105e591810190610cc5565b60015b156105ef57505b8473ffffffffffffffffffffffffffffffffffffffff1663095ea7b38773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610656573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061067a9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff90911660048201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60248201526044016020604051808303816000875af1925050508015610745575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261074291810190610cc5565b60015b1561074c57505b60008573ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308973ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107da9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa15801561084a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086e9190610c48565b905084811015610900576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c73000000000000000000000000000000000000000000006064820152608401610233565b505b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa15801561096f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109939190610c48565b905084811015610acc5773ffffffffffffffffffffffffffffffffffffffff831663494666b6876109c48489610c61565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b158015610a2f57600080fd5b505af1925050508015610a40575060015b610acc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b50505050505050565b600080610b0360017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610c61565b8054600190915592915050565b600080600060408486031215610b2557600080fd5b83359250602084013567ffffffffffffffff80821115610b4457600080fd5b818601915086601f830112610b5857600080fd5b813581811115610b6757600080fd5b876020828501011115610b7957600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610bae57600080fd5b50565b600080600080600060a08688031215610bc957600080fd5b8535610bd481610b8c565b94506020860135610be481610b8c565b9350604086013592506060860135610bfb81610b8c565b91506080860135610c0b81610b8c565b809150509295509295909350565b6000825160005b81811015610c3a5760208186018101518583015201610c20565b506000920191825250919050565b600060208284031215610c5a57600080fd5b5051919050565b81810381811115610c9b577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610cb357600080fd5b8151610cbe81610b8c565b9392505050565b600060208284031215610cd757600080fd5b81518015158114610cbe57600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/solidity/Solver.sol b/crates/contracts/solidity/Solver.sol index cf6aea29ac..f8fd460623 100644 --- a/crates/contracts/solidity/Solver.sol +++ b/crates/contracts/solidity/Solver.sol @@ -17,11 +17,6 @@ contract Solver { using Caller for *; using Math for *; - struct Mock { - bool enabled; - address spardose; - } - uint256 private _simulationOverhead; uint256[] private _queriedBalances; @@ -32,49 +27,23 @@ contract Solver { /// /// @param settlementContract - address of the settlement contract because /// it does not have a stable address in tests. - /// @param trader - address of the order owner doing the trade - /// @param sellToken - address of the token being sold - /// @param sellAmount - amount being sold - /// @param nativeToken - ERC20 version of the chain's token /// @param tokens - list of tokens used in the trade /// @param receiver - address receiving the bought tokens /// @param settlementCall - the calldata of the `settle()` call - /// @param mock - mocking configuration for the simulation; this controls - /// whether things like ETH wrapping, setting allowance and - /// pre-funding should be done on behalf of the user to support - /// quote verification for users who aren't ready to swap. /// /// @return gasUsed - gas used for the `settle()` call /// @return queriedBalances - list of balances stored during the simulation function swap( ISettlement settlementContract, - address payable trader, - address sellToken, - uint256 sellAmount, - address nativeToken, address[] calldata tokens, address payable receiver, - bytes calldata settlementCall, - Mock memory mock + bytes calldata settlementCall ) external returns ( uint256 gasUsed, uint256[] memory queriedBalances ) { require(msg.sender == address(this), "only simulation logic is allowed to call 'swap' function"); - if (mock.enabled) { - // Prepare the trade in the context of the trader so we are allowed - // to set approvals and things like that. - Trader(trader) - .prepareSwap( - settlementContract, - sellToken, - sellAmount, - nativeToken, - mock.spardose - ); - } - // Warm the storage for sending ETH to smart contract addresses. // We allow this call to revert becaues it was either unnecessary in the first place // or failing to send `ETH` to the `receiver` will cause a revert in the settlement @@ -135,4 +104,28 @@ contract Solver { address(settlementContract).doCall(settlementCall); gasUsed = gasStart - gasleft() - _simulationOverhead; } + + /// @dev Simple wrapper around `Trader.ensureTradePreconditions()` that + /// discounts the gas used to prepare the swap (setting up approvals + /// and balances) from the total gas cost since that would normally + /// not happen during the settlement. + function ensureTradePreconditions( + Trader trader, + ISettlement settlementContract, + address sellToken, + uint256 sellAmount, + address nativeToken, + address spardose + ) external { + uint256 gasStart = gasleft(); + trader.ensureTradePreconditions( + settlementContract, + sellToken, + sellAmount, + nativeToken, + spardose + ); + // Account for costs of gas used outside of metered section. + _simulationOverhead += gasStart - gasleft() + 4460; + } } diff --git a/crates/contracts/solidity/Trader.sol b/crates/contracts/solidity/Trader.sol index 695bcdd4f1..fb86b5954d 100644 --- a/crates/contracts/solidity/Trader.sol +++ b/crates/contracts/solidity/Trader.sol @@ -70,7 +70,7 @@ contract Trader { /// @param sellAmount - expected amount to be sold according to the quote /// @param nativeToken - ERC20 version of the chain's native token /// @param spardose - piggy bank for requesting additional funds - function prepareSwap( + function ensureTradePreconditions( ISettlement settlementContract, address sellToken, uint256 sellAmount, @@ -104,8 +104,13 @@ contract Trader { // We first reset the allowance to 0 since some ERC20 tokens (e.g. USDT) // require that due to this attack: // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - IERC20(sellToken).safeApprove(address(settlementContract.vaultRelayer()), 0); - IERC20(sellToken).safeApprove(address(settlementContract.vaultRelayer()), type(uint256).max); + // We catch reverts because we'll later assert the correct approval got set anyway. + try IERC20(sellToken).approve(address(settlementContract.vaultRelayer()), 0) {} + catch {} + try IERC20(sellToken).approve(address(settlementContract.vaultRelayer()), type(uint256).max) {} + catch {} + uint256 allowance = IERC20(sellToken).allowance(address(this), address(settlementContract.vaultRelayer())); + require(allowance >= sellAmount, "trader did not give the required approvals"); } // Ensure that the user has sufficient sell token balance. If not, request some diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 3b50d7ecc3..66ce0eadf9 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -9,6 +9,7 @@ use { quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, }, number::nonzero::U256 as NonZeroU256, + serde_json::json, shared::{ price_estimation::{ trade_verifier::{ @@ -435,4 +436,38 @@ async fn verified_quote_with_simulated_balance(web3: Web3) { .await .unwrap(); assert!(response.verified); + + // Previously quote verification did not set up the trade correctly + // if the user provided pre-interactions. This works now. + let response = services + .submit_quote(&OrderQuoteRequest { + from: H160::zero(), + sell_token: weth.address(), + buy_token: token.address(), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::BeforeFee { + value: to_wei(1).try_into().unwrap(), + }, + }, + app_data: model::order::OrderCreationAppData::Full { + full: json!({ + "metadata": { + "hooks": { + "pre": [ + { + "target": "0x0000000000000000000000000000000000000000", + "callData": "0x", + "gasLimit": "0" + } + ] + } + } + }) + .to_string(), + }, + ..Default::default() + }) + .await + .unwrap(); + assert!(response.verified); } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 7098695c91..dd99562cb2 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -143,6 +143,7 @@ impl TradeVerifier { out_amount, self.native_token, &self.domain_separator, + self.settlement.address(), )?; let settlement = add_balance_queries(settlement, query, &verification, &solver); @@ -158,28 +159,13 @@ impl TradeVerifier { ) .tx; - let sell_amount = match query.kind { - OrderKind::Sell => query.in_amount.get(), - OrderKind::Buy => *out_amount, - }; - - // Only enable additional mocking (approvals, native token wrapping, - // balance overrides) if the user did not provide pre-interactions. If - // the user did provide pre-interactions, it's reasonable to assume that - // they will set up all the necessary details of the trade. - let mock_enabled = verification.pre_interactions.is_empty(); let simulation = solver .methods() .swap( self.settlement.address(), - verification.from, - query.sell_token, - sell_amount, - self.native_token, tokens.clone(), verification.receiver, Bytes(settlement.data.unwrap().0), - (mock_enabled, Self::SPARDOSE), ) .tx; @@ -471,6 +457,7 @@ fn encode_settlement( out_amount: &U256, native_token: H160, domain_separator: &DomainSeparator, + settlement: H160, ) -> Result { let mut trade_interactions = encode_interactions(&trade.interactions()); if query.buy_token == BUY_ETH_ADDRESS { @@ -483,7 +470,13 @@ fn encode_settlement( OrderKind::Buy => query.in_amount.get(), }; let weth = dummy_contract!(WETH9, native_token); - let calldata = weth.methods().withdraw(buy_amount).tx.data.unwrap().0; + let calldata = weth + .methods() + .withdraw(buy_amount) + .tx + .data + .expect("data gets populated by function call above") + .0; trade_interactions.push((native_token, 0.into(), Bytes(calldata))); tracing::trace!("adding unwrap interaction for paying out ETH"); } @@ -498,11 +491,42 @@ fn encode_settlement( )?); } - let pre_interactions = [ - verification.pre_interactions.clone(), - trade.pre_interactions(), - ] - .concat(); + // Execute interaction to set up trade right before transfering funds. + // This interaction does nothing if the user-provided pre-interactions + // already set everything up (e.g. approvals, balances). That way we can + // correctly verify quotes with or without these user pre-interactions + // with helpful error messages. + let trade_setup_interaction = { + let sell_amount = match query.kind { + OrderKind::Sell => query.in_amount.get(), + OrderKind::Buy => *out_amount, + }; + let solver = dummy_contract!(Solver, trade.solver()); + let setup_call = solver + .ensure_trade_preconditions( + verification.from, + settlement, + query.sell_token, + sell_amount, + native_token, + TradeVerifier::SPARDOSE, + ) + .tx + .data + .expect("data gets populated by function call above") + .0; + Interaction { + target: solver.address(), + value: 0.into(), + data: setup_call, + } + }; + + let user_interactions = verification.pre_interactions.iter().cloned(); + let pre_interactions: Vec<_> = user_interactions + .chain(trade.pre_interactions()) + .chain([trade_setup_interaction]) + .collect(); Ok(EncodedSettlement { tokens: tokens.to_vec(), @@ -668,9 +692,14 @@ fn add_balance_queries( // track how much `sell_token` the `from` address actually spent OrderKind::Buy => (query.sell_token, verification.from), }; - let query_balance = solver.methods().store_balance(token, owner, true); - let query_balance = Bytes(query_balance.tx.data.unwrap().0); - let interaction = (solver.address(), 0.into(), query_balance); + let query_balance_call = solver + .methods() + .store_balance(token, owner, true) + .tx + .data + .expect("data gets populated by function call above") + .0; + let interaction = (solver.address(), 0.into(), Bytes(query_balance_call)); // query balance query at the end of pre-interactions settlement.interactions[0].push(interaction.clone()); // query balance right after we payed out all `buy_token` From ab3ec97ff2c8b7b9f566cc1d8311530cb408e14d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Strug?= <47604705+mstrug@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:49:33 +0100 Subject: [PATCH 16/25] Storing pre-interactions and Jit orders in quote metadata (#3162) # Description Added storing of additional quote metadata: pre-interactions and jit orders. Those data can be used for debugging/analysis of the quotes and orders executed by the winning auction. # Changes Extended `QuoteMetadataV1` struct with new fields (vectors): `pre_interactions` and `jit_orders`. Extended `QuoteExecution` struct in trade finding quote with same fields as `QuoteMetadataV1`. Added filling of `QuoteExecution` new fields from price estimation results and then moving them to `QuoteMetadataV1` and storing in database as json. :warning: This PR needs to be released together with PR #3124, which introduced `QuoteMetadataV1`. Otherwise this PR needs to be updated to use new type: `QuoteMetadataV2` for properly handling version 1 data already stored in database. ## How to test Existing tests. Also unit tests were updated to verify serializaion/deserialization of new fields. --- crates/e2e/tests/e2e/quote_verification.rs | 2 + crates/orderbook/src/database/orders.rs | 6 ++ crates/shared/src/order_quoting.rs | 76 +++++++++++++++++-- .../src/price_estimation/trade_verifier.rs | 6 ++ crates/shared/src/trade_finding/external.rs | 6 +- crates/shared/src/trade_finding/mod.rs | 9 +++ 6 files changed, 96 insertions(+), 9 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 66ce0eadf9..b189cc0d06 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -183,6 +183,8 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { value: 0.into(), call_data: hex::decode("aa77476c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000e357b42c3a9d8ccf0000000000000000000000000000000000000000000000000000000004d0e79e000000000000000000000000a69babef1ca67a37ffaf7a485dfff3382056e78c0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066360af101ffffffffffffffffffffffffffffffffffffff0f3f47f166360a8d0000003f0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001c66b3383f287dd9c85ad90e7c5a576ea4ba1bdf5a001d794a9afa379e6b2517b47e487a1aef32e75af432cbdbd301ada42754eaeac21ec4ca744afd92732f47540000000000000000000000000000000000000000000000000000000004d0c80f").unwrap() }], + pre_interactions: vec![], + jit_orders: vec![], }, }; diff --git a/crates/orderbook/src/database/orders.rs b/crates/orderbook/src/database/orders.rs index 4a1d78fd30..a21884f9c1 100644 --- a/crates/orderbook/src/database/orders.rs +++ b/crates/orderbook/src/database/orders.rs @@ -1234,6 +1234,12 @@ mod tests { call_data: vec![2, 20], }, ], + pre_interactions: vec![InteractionData { + target: H160([3; 20]), + value: U256::from(30), + call_data: vec![3, 20], + }], + jit_orders: vec![], } .into(), ..Default::default() diff --git a/crates/shared/src/order_quoting.rs b/crates/shared/src/order_quoting.rs index 6942655449..60a78a141b 100644 --- a/crates/shared/src/order_quoting.rs +++ b/crates/shared/src/order_quoting.rs @@ -11,6 +11,7 @@ use { fee::FeeParameters, order_validation::PreOrderData, price_estimation::{Estimate, QuoteVerificationMode, Verification}, + trade_finding::external::dto, }, anyhow::{Context, Result}, chrono::{DateTime, Duration, Utc}, @@ -244,7 +245,7 @@ pub enum FindQuoteError { NotFound(Option), #[error("quote does not match parameters")] - ParameterMismatch(QuoteData), + ParameterMismatch(Box), #[error("quote expired")] Expired(DateTime), @@ -447,6 +448,8 @@ impl OrderQuoter { verified: trade_estimate.verified, metadata: QuoteMetadataV1 { interactions: trade_estimate.execution.interactions, + pre_interactions: trade_estimate.execution.pre_interactions, + jit_orders: trade_estimate.execution.jit_orders, } .into(), }; @@ -576,7 +579,7 @@ impl OrderQuoting for OrderQuoter { .ok_or(FindQuoteError::NotFound(Some(id)))?; if !parameters.matches(&data) { - return Err(FindQuoteError::ParameterMismatch(data)); + return Err(FindQuoteError::ParameterMismatch(Box::new(data))); } if data.expiration < now { return Err(FindQuoteError::Expired(data.expiration)); @@ -687,9 +690,15 @@ impl From for QuoteMetadata { } #[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] pub struct QuoteMetadataV1 { /// Data provided by the solver in response to /quote request. pub interactions: Vec, + /// The onchain calls to run before sending user funds to the settlement + /// contract. + pub pre_interactions: Vec, + /// Orders that were settled outside of the auction. + pub jit_orders: Vec, } #[cfg(test)] @@ -1643,6 +1652,34 @@ mod tests { call_data: vec![2], }, ], + pre_interactions: vec![ + InteractionData { + target: H160::from([3; 20]), + value: U256::from(3), + call_data: vec![3], + }, + InteractionData { + target: H160::from([4; 20]), + value: U256::from(4), + call_data: vec![4], + }, + ], + jit_orders: vec![dto::JitOrder { + buy_token: H160([4; 20]), + sell_token: H160([5; 20]), + sell_amount: U256::from(10), + buy_amount: U256::from(20), + executed_amount: U256::from(11), + receiver: H160([6; 20]), + valid_to: 1734084318, + app_data: Default::default(), + side: dto::Side::Sell, + partially_fillable: false, + sell_token_source: model::order::SellTokenSource::External, + buy_token_destination: model::order::BuyTokenDestination::Internal, + signature: vec![1; 16], + signing_scheme: model::signature::SigningScheme::Eip712, + }], } .into(); let v = serde_json::to_value(q).unwrap(); @@ -1652,8 +1689,16 @@ mod tests { {"version":"1.0", "interactions":[ {"target":"0x0101010101010101010101010101010101010101","value":"1","callData":"0x01"}, - {"target":"0x0202020202020202020202020202020202020202","value":"2","callData":"0x02"} - ]}"#, + {"target":"0x0202020202020202020202020202020202020202","value":"2","callData":"0x02"}], + "preInteractions":[ + {"target":"0x0303030303030303030303030303030303030303","value":"3","callData":"0x03"}, + {"target":"0x0404040404040404040404040404040404040404","value":"4","callData":"0x04"}], + "jitOrders":[{"appData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "buyAmount": "20", "buyToken": "0x0404040404040404040404040404040404040404", "buyTokenDestination": "internal", + "executedAmount": "11", "partiallyFillable": false, "receiver": "0x0606060606060606060606060606060606060606", + "sellAmount": "10", "sellToken": "0x0505050505050505050505050505050505050505", "sellTokenSource": "external", + "side": "sell", "signature": "0x01010101010101010101010101010101", "signingScheme": "eip712", "validTo": 1734084318}] + }"#, ) .unwrap(); @@ -1675,14 +1720,31 @@ mod tests { {"version":"1.0", "interactions":[ {"target":"0x0101010101010101010101010101010101010101","value":"1","callData":"0x01"}, - {"target":"0x0202020202020202020202020202020202020202","value":"2","callData":"0x02"} - ]}"#, + {"target":"0x0202020202020202020202020202020202020202","value":"2","callData":"0x02"}], + "preInteractions":[ + {"target":"0x0303030303030303030303030303030303030303","value":"3","callData":"0x03"}, + {"target":"0x0404040404040404040404040404040404040404","value":"4","callData":"0x04"}], + "jitOrders":[{"appData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "buyAmount": "20", "buyToken": "0x0404040404040404040404040404040404040404", "buyTokenDestination": "internal", + "executedAmount": "11", "partiallyFillable": false, "receiver": "0x0606060606060606060606060606060606060606", + "sellAmount": "10", "sellToken": "0x0505050505050505050505050505050505050505", "sellTokenSource": "external", + "side": "sell", "signature": "0x01010101010101010101010101010101", "signingScheme": "eip712", "validTo": 1734084318}, + {"appData": "0x0ddeb6e4a814908832cc25d11311c514e7efe6af3c9bafeb0d241129cf7f4d83", + "buyAmount": "100", "buyToken": "0x0606060606060606060606060606060606060606", "buyTokenDestination": "erc20", + "executedAmount": "99", "partiallyFillable": true, "receiver": "0x0303030303030303030303030303030303030303", + "sellAmount": "10", "sellToken": "0x0101010101010101010101010101010101010101", "sellTokenSource": "erc20", + "side": "buy", "signature": "0x01010101010101010101010101010101", "signingScheme": "eip1271", "validTo": 1734085109}] + }"#, ) .unwrap(); let metadata: QuoteMetadata = v1.try_into().unwrap(); match metadata { - QuoteMetadata::V1(v1) => assert_eq!(v1.interactions.len(), 2), + QuoteMetadata::V1(v1) => { + assert_eq!(v1.interactions.len(), 2); + assert_eq!(v1.pre_interactions.len(), 2); + assert_eq!(v1.jit_orders.len(), 2); + } } } } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index dd99562cb2..f6a80b56b6 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -206,6 +206,8 @@ impl TradeVerifier { verified: true, execution: QuoteExecution { interactions: map_interactions_data(&trade.interactions()), + pre_interactions: map_interactions_data(&trade.pre_interactions()), + jit_orders: trade.jit_orders(), }, }; tracing::warn!( @@ -417,6 +419,8 @@ impl TradeVerifying for TradeVerifier { verified: false, execution: QuoteExecution { interactions: map_interactions_data(&trade.interactions()), + pre_interactions: map_interactions_data(&trade.pre_interactions()), + jit_orders: trade.jit_orders(), }, }; tracing::warn!( @@ -809,6 +813,8 @@ fn ensure_quote_accuracy( verified: true, execution: QuoteExecution { interactions: map_interactions_data(&trade.interactions()), + pre_interactions: map_interactions_data(&trade.pre_interactions()), + jit_orders: trade.jit_orders(), }, }) } diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 95e820eb70..1ab83f9d0f 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -224,6 +224,8 @@ impl TradeFinding for ExternalTradeFinder { solver: trade.solver(), execution: QuoteExecution { interactions: map_interactions_data(&trade.interactions()), + pre_interactions: map_interactions_data(&trade.pre_interactions()), + jit_orders: trade.jit_orders(), }, }) } @@ -312,7 +314,7 @@ pub(crate) mod dto { } #[serde_as] - #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] + #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[allow(unused)] pub struct JitOrder { @@ -345,7 +347,7 @@ pub(crate) mod dto { } #[serde_as] - #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] + #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum Side { Buy, diff --git a/crates/shared/src/trade_finding/mod.rs b/crates/shared/src/trade_finding/mod.rs index 0c0cc9a5ce..20318b13f7 100644 --- a/crates/shared/src/trade_finding/mod.rs +++ b/crates/shared/src/trade_finding/mod.rs @@ -43,6 +43,8 @@ pub struct Quote { #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] pub struct QuoteExecution { pub interactions: Vec, + pub pre_interactions: Vec, + pub jit_orders: Vec, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -107,6 +109,13 @@ impl TradeKind { TradeKind::Regular(trade) => trade.pre_interactions.clone(), } } + + pub fn jit_orders(&self) -> Vec { + match self { + TradeKind::Legacy(_) => Vec::new(), + TradeKind::Regular(trade) => trade.jit_orders.clone(), + } + } } /// A legacy trade. From 6fe5635e25215a7e5797fdede5363bca15131732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Strug?= <47604705+mstrug@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:00:23 +0100 Subject: [PATCH 17/25] [TRIVIAL] Fixed driver OpenAPI /reveal request definition (#3164) # Description OpenAPI driver `/reveal` request has 3 fields: `solutionId`, `submissionDeadlineLatestBlock`, `auctionId` which is inconsistent with the source code, where `/reveal` request has only 2 fields: `solutionId` and `auctionId`: [source](https://github.com/cowprotocol/services/blob/main/crates/driver/src/infra/api/routes/reveal/dto/solution.rs). # Changes `/settle` and `/reveal` endpoints have same API definition: `Solution`, so I've created new API component: `SolutionWithDeadline` and assigned it to `/settle` endpoint. From `Solution` I've removed `submissionDeadlineLatestBlock` field which now aligns with `/reveal` endpoint source code. Renamed driver settle dto module `solution.rs` to `solution_with_deadline.rs` and name of the struct `Solution` to `SolutionWithDeadline`. --------- Co-authored-by: Martin Magnus Co-authored-by: ilya --- crates/driver/openapi.yml | 24 ++++++++++++++----- .../src/infra/api/routes/reveal/dto/mod.rs | 6 ++--- .../dto/{solution.rs => reveal_request.rs} | 2 +- .../dto/{revealed.rs => reveal_response.rs} | 4 ++-- .../driver/src/infra/api/routes/reveal/mod.rs | 6 ++--- .../src/infra/api/routes/settle/dto/mod.rs | 4 ++-- .../dto/{solution.rs => settle_request.rs} | 2 +- .../driver/src/infra/api/routes/settle/mod.rs | 2 +- .../src/infra/api/routes/solve/dto/mod.rs | 8 +++---- .../dto/{auction.rs => solve_request.rs} | 6 ++--- .../dto/{solved.rs => solve_response.rs} | 4 ++-- .../driver/src/infra/api/routes/solve/mod.rs | 13 ++++++---- 12 files changed, 48 insertions(+), 33 deletions(-) rename crates/driver/src/infra/api/routes/reveal/dto/{solution.rs => reveal_request.rs} (94%) rename crates/driver/src/infra/api/routes/reveal/dto/{revealed.rs => reveal_response.rs} (93%) rename crates/driver/src/infra/api/routes/settle/dto/{solution.rs => settle_request.rs} (95%) rename crates/driver/src/infra/api/routes/solve/dto/{auction.rs => solve_request.rs} (99%) rename crates/driver/src/infra/api/routes/solve/dto/{solved.rs => solve_response.rs} (98%) diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index 9b4a6bd812..a8d43d199f 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -103,14 +103,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Solution" + $ref: "#/components/schemas/RevealRequest" responses: "200": description: Execution accepted. content: application/json: schema: - $ref: "#/components/schemas/RevealedResponse" + $ref: "#/components/schemas/RevealResponse" "400": $ref: "#/components/responses/BadRequest" "500": @@ -130,7 +130,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Solution" + $ref: "#/components/schemas/SettleRequest" responses: "200": description: Execution accepted. @@ -525,8 +525,8 @@ components: $ref: "#/components/schemas/BigUint" gas: type: integer - Solution: - description: Request to the settle and reveal endpoint. + SettleRequest: + description: Request to the `/settle` endpoint. type: object properties: solutionId: @@ -541,7 +541,19 @@ components: description: Auction ID in which the specified solution ID is competing. type: integer example: 123 - RevealedResponse: + RevealRequest: + description: Request to the `/reveal` endpoint. + type: object + properties: + solutionId: + description: Id of the solution that should be executed. + type: integer + example: 123 + auctionId: + description: Auction ID in which the specified solution ID is competing. + type: integer + example: 123 + RevealResponse: description: Response of the reveal endpoint. type: object properties: diff --git a/crates/driver/src/infra/api/routes/reveal/dto/mod.rs b/crates/driver/src/infra/api/routes/reveal/dto/mod.rs index 164d4534c4..75671089e4 100644 --- a/crates/driver/src/infra/api/routes/reveal/dto/mod.rs +++ b/crates/driver/src/infra/api/routes/reveal/dto/mod.rs @@ -1,4 +1,4 @@ -mod revealed; -mod solution; +mod reveal_request; +mod reveal_response; -pub use {revealed::Revealed, solution::Solution}; +pub use {reveal_request::RevealRequest, reveal_response::RevealResponse}; diff --git a/crates/driver/src/infra/api/routes/reveal/dto/solution.rs b/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs similarity index 94% rename from crates/driver/src/infra/api/routes/reveal/dto/solution.rs rename to crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs index 0c8d55fa49..f298b55700 100644 --- a/crates/driver/src/infra/api/routes/reveal/dto/solution.rs +++ b/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs @@ -3,7 +3,7 @@ use {super::super::super::deserialize_solution_id, serde::Deserialize, serde_wit #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Solution { +pub struct RevealRequest { /// Unique ID of the solution (per driver competition), to reveal. #[serde(deserialize_with = "deserialize_solution_id")] pub solution_id: u64, diff --git a/crates/driver/src/infra/api/routes/reveal/dto/revealed.rs b/crates/driver/src/infra/api/routes/reveal/dto/reveal_response.rs similarity index 93% rename from crates/driver/src/infra/api/routes/reveal/dto/revealed.rs rename to crates/driver/src/infra/api/routes/reveal/dto/reveal_response.rs index 1f256320b4..0f1d495042 100644 --- a/crates/driver/src/infra/api/routes/reveal/dto/revealed.rs +++ b/crates/driver/src/infra/api/routes/reveal/dto/reveal_response.rs @@ -4,7 +4,7 @@ use { serde_with::serde_as, }; -impl Revealed { +impl RevealResponse { pub fn new(reveal: competition::Revealed) -> Self { Self { calldata: Calldata { @@ -18,7 +18,7 @@ impl Revealed { #[serde_as] #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Revealed { +pub struct RevealResponse { calldata: Calldata, } diff --git a/crates/driver/src/infra/api/routes/reveal/mod.rs b/crates/driver/src/infra/api/routes/reveal/mod.rs index 750636cdaf..b96ce290f8 100644 --- a/crates/driver/src/infra/api/routes/reveal/mod.rs +++ b/crates/driver/src/infra/api/routes/reveal/mod.rs @@ -14,8 +14,8 @@ pub(in crate::infra::api) fn reveal(router: axum::Router) -> axum::Router async fn route( state: axum::extract::State, - req: axum::Json, -) -> Result, (hyper::StatusCode, axum::Json)> { + req: axum::Json, +) -> Result, (hyper::StatusCode, axum::Json)> { let handle_request = async { observe::revealing(); let result = state @@ -24,7 +24,7 @@ async fn route( .await; observe::revealed(state.solver().name(), &result); let result = result?; - Ok(axum::Json(dto::Revealed::new(result))) + Ok(axum::Json(dto::RevealResponse::new(result))) }; handle_request diff --git a/crates/driver/src/infra/api/routes/settle/dto/mod.rs b/crates/driver/src/infra/api/routes/settle/dto/mod.rs index 563b0e3cb7..0986792238 100644 --- a/crates/driver/src/infra/api/routes/settle/dto/mod.rs +++ b/crates/driver/src/infra/api/routes/settle/dto/mod.rs @@ -1,3 +1,3 @@ -mod solution; +mod settle_request; -pub use solution::Solution; +pub use settle_request::SettleRequest; diff --git a/crates/driver/src/infra/api/routes/settle/dto/solution.rs b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs similarity index 95% rename from crates/driver/src/infra/api/routes/settle/dto/solution.rs rename to crates/driver/src/infra/api/routes/settle/dto/settle_request.rs index 6c62cd0cdd..478d0766f2 100644 --- a/crates/driver/src/infra/api/routes/settle/dto/solution.rs +++ b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs @@ -3,7 +3,7 @@ use {super::super::super::deserialize_solution_id, serde::Deserialize, serde_wit #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Solution { +pub struct SettleRequest { /// Unique ID of the solution (per driver competition), to settle. #[serde(deserialize_with = "deserialize_solution_id")] pub solution_id: u64, diff --git a/crates/driver/src/infra/api/routes/settle/mod.rs b/crates/driver/src/infra/api/routes/settle/mod.rs index 6a169ce3b1..f6d073e1c7 100644 --- a/crates/driver/src/infra/api/routes/settle/mod.rs +++ b/crates/driver/src/infra/api/routes/settle/mod.rs @@ -17,7 +17,7 @@ pub(in crate::infra::api) fn settle(router: axum::Router) -> axum::Router async fn route( state: axum::extract::State, - req: axum::Json, + req: axum::Json, ) -> Result<(), (hyper::StatusCode, axum::Json)> { let auction_id = req.auction_id; let solver = state.solver().name().to_string(); diff --git a/crates/driver/src/infra/api/routes/solve/dto/mod.rs b/crates/driver/src/infra/api/routes/solve/dto/mod.rs index b7d9b72236..1fb6e69db5 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/mod.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/mod.rs @@ -1,7 +1,7 @@ -mod auction; -mod solved; +mod solve_request; +mod solve_response; pub use { - auction::{Auction, Error as AuctionError}, - solved::Solved, + solve_request::{Error as AuctionError, SolveRequest}, + solve_response::SolveResponse, }; diff --git a/crates/driver/src/infra/api/routes/solve/dto/auction.rs b/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs similarity index 99% rename from crates/driver/src/infra/api/routes/solve/dto/auction.rs rename to crates/driver/src/infra/api/routes/solve/dto/solve_request.rs index 38138face1..b773387715 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/auction.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs @@ -13,7 +13,7 @@ use { std::collections::HashSet, }; -impl Auction { +impl SolveRequest { pub async fn into_domain( self, eth: &Ethereum, @@ -198,7 +198,7 @@ impl From for Error { #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Auction { +pub struct SolveRequest { #[serde_as(as = "serde_with::DisplayFromStr")] id: i64, tokens: Vec, @@ -208,7 +208,7 @@ pub struct Auction { surplus_capturing_jit_order_owners: Vec, } -impl Auction { +impl SolveRequest { pub fn id(&self) -> i64 { self.id } diff --git a/crates/driver/src/infra/api/routes/solve/dto/solved.rs b/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs similarity index 98% rename from crates/driver/src/infra/api/routes/solve/dto/solved.rs rename to crates/driver/src/infra/api/routes/solve/dto/solve_response.rs index 6b91523be0..892810f5f1 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/solved.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs @@ -9,7 +9,7 @@ use { std::collections::HashMap, }; -impl Solved { +impl SolveResponse { pub fn new(solved: Option, solver: &Solver) -> Self { let solutions = solved .into_iter() @@ -22,7 +22,7 @@ impl Solved { #[serde_as] #[derive(Debug, Default, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Solved { +pub struct SolveResponse { solutions: Vec, } diff --git a/crates/driver/src/infra/api/routes/solve/mod.rs b/crates/driver/src/infra/api/routes/solve/mod.rs index eccafb8ead..be9de73abe 100644 --- a/crates/driver/src/infra/api/routes/solve/mod.rs +++ b/crates/driver/src/infra/api/routes/solve/mod.rs @@ -17,13 +17,13 @@ pub(in crate::infra::api) fn solve(router: axum::Router) -> axum::Router< async fn route( state: axum::extract::State, - auction: axum::Json, -) -> Result, (hyper::StatusCode, axum::Json)> { - let auction_id = auction.id(); + req: axum::Json, +) -> Result, (hyper::StatusCode, axum::Json)> { + let auction_id = req.id(); let handle_request = async { observe::auction(auction_id); let start = Instant::now(); - let auction = auction + let auction = req .0 .into_domain(state.eth(), state.tokens(), state.timeouts()) .await @@ -38,7 +38,10 @@ async fn route( .await; let result = competition.solve(&auction).await; observe::solved(state.solver().name(), &result); - Ok(axum::Json(dto::Solved::new(result?, &competition.solver))) + Ok(axum::Json(dto::SolveResponse::new( + result?, + &competition.solver, + ))) }; handle_request From 53ec0677f0a59c2c93770a86117cd16aaa1d3996 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 17 Dec 2024 12:09:40 +0000 Subject: [PATCH 18/25] [EASY] Improve settlement tx cancellation logging (#3165) # Description ATM, it is not possible to understand whether a settlement cancellation tx succeeded. This PR adds logs with the cancellation tx hash, so it will be possible to check the transaction on chain later. Also, additional context is added when working with the cancellation tx submission since, currently, it is impossible to distinguish between the actual settlement tx submission error. --- crates/driver/src/domain/mempools.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/driver/src/domain/mempools.rs b/crates/driver/src/domain/mempools.rs index 0d5df67404..6200caaeb3 100644 --- a/crates/driver/src/domain/mempools.rs +++ b/crates/driver/src/domain/mempools.rs @@ -4,9 +4,14 @@ use { eth, }, crate::{ - domain::{competition::solution::Settlement, eth::TxStatus, BlockNo}, + domain::{ + competition::solution::Settlement, + eth::{TxId, TxStatus}, + BlockNo, + }, infra::{self, observe, solver::Solver, Ethereum}, }, + anyhow::Context, ethrpc::block_stream::into_stream, futures::{future::select_ok, FutureExt, StreamExt}, thiserror::Error, @@ -123,24 +128,32 @@ impl Mempools { TxStatus::Pending => { // Check if the current block reached the submission deadline block number if block.number >= submission_deadline { + let cancellation_tx_hash = self + .cancel(mempool, settlement.gas.price, solver) + .await + .context("cancellation tx due to deadline failed")?; tracing::info!( - ?hash, + settle_tx_hash = ?hash, deadline = submission_deadline, current_block = block.number, + ?cancellation_tx_hash, "tx not confirmed in time, cancelling", ); - self.cancel(mempool, settlement.gas.price, solver).await?; return Err(Error::Expired); } // Check if transaction still simulates if let Err(err) = self.ethereum.estimate_gas(tx).await { if err.is_revert() { + let cancellation_tx_hash = self + .cancel(mempool, settlement.gas.price, solver) + .await + .context("cancellation tx due to revert failed")?; tracing::info!( - ?hash, + settle_tx_hash = ?hash, + ?cancellation_tx_hash, ?err, "tx started failing in mempool, cancelling" ); - self.cancel(mempool, settlement.gas.price, solver).await?; return Err(Error::SimulationRevert); } else { tracing::warn!(?hash, ?err, "couldn't re-simulate tx"); @@ -173,7 +186,7 @@ impl Mempools { mempool: &infra::mempool::Mempool, pending: eth::GasPrice, solver: &Solver, - ) -> Result<(), Error> { + ) -> Result { let cancellation = eth::Tx { from: solver.address(), to: solver.address(), @@ -186,8 +199,7 @@ impl Mempools { limit: CANCELLATION_GAS_AMOUNT.into(), price: pending * GAS_PRICE_BUMP, }; - mempool.submit(cancellation, gas, solver).await?; - Ok(()) + mempool.submit(cancellation, gas, solver).await } } From ec5c475c167152fc4e531126910319411fad73f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Strug?= <47604705+mstrug@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:58:59 +0100 Subject: [PATCH 19/25] [TRIVIAL] Removed undo migration script (#3167) # Description Flyway community version is not supporting undo migrations ([link](https://www.red-gate.com/products/flyway/editions)) so removing undo migration script, as it causes warning in the logs during applying database migrations. # Changes Removed one file. --- .../U075__add_metadata_to_quotes_and_order_quotes.sql | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 database/sql/U075__add_metadata_to_quotes_and_order_quotes.sql diff --git a/database/sql/U075__add_metadata_to_quotes_and_order_quotes.sql b/database/sql/U075__add_metadata_to_quotes_and_order_quotes.sql deleted file mode 100644 index e0f15152ff..0000000000 --- a/database/sql/U075__add_metadata_to_quotes_and_order_quotes.sql +++ /dev/null @@ -1,11 +0,0 @@ --- This script reverts changes applied in V075__add_metadata_to_quotes_and_order_quotes.sql migration script. - --- Step 1: Drop two columns from the quotes table -ALTER TABLE quotes - DROP COLUMN verified, - DROP COLUMN metadata; - --- Step 2: Drop two columns from the order_quotes table -ALTER TABLE order_quotes - DROP COLUMN verified, - DROP COLUMN metadata; From 8761e434d92cba2cca15757c39d9616f7067efdc Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Tue, 17 Dec 2024 16:08:30 +0100 Subject: [PATCH 20/25] Fix quote verifications for USDT (#3168) # Description https://github.com/cowprotocol/services/pull/3160 introduced problems with verifying quotes with `USDT` as the sell token. The reason is that `USDT` is not ERC20 compliant and does not return a `bool` from `approve()` as it's supposed to. This then leads to reverts like [this](https://dashboard.tenderly.co/cow-protocol/production/simulator/6042dc35-e2ef-4732-959b-aba737842982) where solidity is not able to decode which function to call afterwards because the previous `approve()` messed up the memory. # Changes Revert back to using `safeApprove()` which handles this case gracefully. Unfortunately we can't use `try-catch` with `safeApprove()` because it's not an `external` function. But that will not make the simulation less correct. It will just cause us to get a suboptimal error message in case one of the approvals actually reverts. ## How to test Added a forked e2e test that verifies a quote with `USDT` as the sell token. This fails without the fix. --- crates/contracts/artifacts/Trader.json | 2 +- crates/contracts/solidity/Trader.sol | 14 +++--- crates/e2e/tests/e2e/quote_verification.rs | 51 ++++++++++++++++++++++ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/crates/contracts/artifacts/Trader.json b/crates/contracts/artifacts/Trader.json index 2884646dee..eeb670b829 100644 --- a/crates/contracts/artifacts/Trader.json +++ b/crates/contracts/artifacts/Trader.json @@ -1 +1 @@ -{"abi":[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address","name":"spardose","type":"address"}],"name":"ensureTradePreconditions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610cf4806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e14610086578063542eb77d146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610b10565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610bb1565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610c19565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa610ad5565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610c48565b9050838110156103835760006103168286610c61565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008473ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308873ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104139190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a79190610c48565b905083811015610902578473ffffffffffffffffffffffffffffffffffffffff1663095ea7b38773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610518573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053c9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152600060248201526044016020604051808303816000875af19250505080156105e8575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526105e591810190610cc5565b60015b156105ef57505b8473ffffffffffffffffffffffffffffffffffffffff1663095ea7b38773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610656573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061067a9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff90911660048201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60248201526044016020604051808303816000875af1925050508015610745575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261074291810190610cc5565b60015b1561074c57505b60008573ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308973ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107da9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa15801561084a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086e9190610c48565b905084811015610900576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c73000000000000000000000000000000000000000000006064820152608401610233565b505b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa15801561096f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109939190610c48565b905084811015610acc5773ffffffffffffffffffffffffffffffffffffffff831663494666b6876109c48489610c61565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b158015610a2f57600080fd5b505af1925050508015610a40575060015b610acc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b50505050505050565b600080610b0360017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610c61565b8054600190915592915050565b600080600060408486031215610b2557600080fd5b83359250602084013567ffffffffffffffff80821115610b4457600080fd5b818601915086601f830112610b5857600080fd5b813581811115610b6757600080fd5b876020828501011115610b7957600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610bae57600080fd5b50565b600080600080600060a08688031215610bc957600080fd5b8535610bd481610b8c565b94506020860135610be481610b8c565b9350604086013592506060860135610bfb81610b8c565b91506080860135610c0b81610b8c565b809150509295509295909350565b6000825160005b81811015610c3a5760208186018101518583015201610c20565b506000920191825250919050565b600060208284031215610c5a57600080fd5b5051919050565b81810381811115610c9b577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610cb357600080fd5b8151610cbe81610b8c565b9392505050565b600060208284031215610cd757600080fd5b81518015158114610cbe57600080fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e14610086578063542eb77d146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610b10565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610bb1565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610c19565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa610ad5565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610c48565b9050838110156103835760006103168286610c61565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008473ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308873ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104139190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa158015610483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a79190610c48565b905083811015610902578473ffffffffffffffffffffffffffffffffffffffff1663095ea7b38773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610518573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053c9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152600060248201526044016020604051808303816000875af19250505080156105e8575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526105e591810190610cc5565b60015b156105ef57505b8473ffffffffffffffffffffffffffffffffffffffff1663095ea7b38773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610656573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061067a9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff90911660048201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60248201526044016020604051808303816000875af1925050508015610745575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261074291810190610cc5565b60015b1561074c57505b60008573ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308973ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107da9190610ca1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa15801561084a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086e9190610c48565b905084811015610900576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c73000000000000000000000000000000000000000000006064820152608401610233565b505b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa15801561096f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109939190610c48565b905084811015610acc5773ffffffffffffffffffffffffffffffffffffffff831663494666b6876109c48489610c61565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b158015610a2f57600080fd5b505af1925050508015610a40575060015b610acc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b50505050505050565b600080610b0360017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610c61565b8054600190915592915050565b600080600060408486031215610b2557600080fd5b83359250602084013567ffffffffffffffff80821115610b4457600080fd5b818601915086601f830112610b5857600080fd5b813581811115610b6757600080fd5b876020828501011115610b7957600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610bae57600080fd5b50565b600080600080600060a08688031215610bc957600080fd5b8535610bd481610b8c565b94506020860135610be481610b8c565b9350604086013592506060860135610bfb81610b8c565b91506080860135610c0b81610b8c565b809150509295509295909350565b6000825160005b81811015610c3a5760208186018101518583015201610c20565b506000920191825250919050565b600060208284031215610c5a57600080fd5b5051919050565b81810381811115610c9b577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610cb357600080fd5b8151610cbe81610b8c565b9392505050565b600060208284031215610cd757600080fd5b81518015158114610cbe57600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address","name":"spardose","type":"address"}],"name":"ensureTradePreconditions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610be0806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e14610086578063542eb77d146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610a09565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610aaa565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610b12565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa610801565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610b41565b9050838110156103835760006103168286610b5a565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190610b94565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff808316602483015291925060009187169063dd62ed3e90604401602060405180830381865afa15801561046e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104929190610b41565b90508481101561062d576104be73ffffffffffffffffffffffffffffffffffffffff871683600061083c565b6104ff73ffffffffffffffffffffffffffffffffffffffff8716837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61083c565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301526000919088169063dd62ed3e90604401602060405180830381865afa158015610575573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105999190610b41565b90508581101561062b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c73000000000000000000000000000000000000000000006064820152608401610233565b505b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa15801561069a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106be9190610b41565b9050858110156107f75773ffffffffffffffffffffffffffffffffffffffff841663494666b6886106ef848a610b5a565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b15801561075a57600080fd5b505af192505050801561076b575060015b6107f7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b5050505050505050565b60008061082f60017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610b5a565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906108cf90861683610947565b90506108da8161095c565b610940576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c65640000000000006044820152606401610233565b5050505050565b606061095583600084610983565b9392505050565b600081516000148061097d57508180602001905181019061097d9190610bb1565b92915050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516109ad9190610b12565b60006040518083038185875af1925050503d80600081146109ea576040519150601f19603f3d011682016040523d82523d6000602084013e6109ef565b606091505b509250905080610a0157815160208301fd5b509392505050565b600080600060408486031215610a1e57600080fd5b83359250602084013567ffffffffffffffff80821115610a3d57600080fd5b818601915086601f830112610a5157600080fd5b813581811115610a6057600080fd5b876020828501011115610a7257600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610aa757600080fd5b50565b600080600080600060a08688031215610ac257600080fd5b8535610acd81610a85565b94506020860135610add81610a85565b9350604086013592506060860135610af481610a85565b91506080860135610b0481610a85565b809150509295509295909350565b6000825160005b81811015610b335760208186018101518583015201610b19565b506000920191825250919050565b600060208284031215610b5357600080fd5b5051919050565b8181038181111561097d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610ba657600080fd5b815161095581610a85565b600060208284031215610bc357600080fd5b8151801515811461095557600080fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e14610086578063542eb77d146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a1366004610a09565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610aaa565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610b12565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa610801565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610385576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610b41565b9050838110156103835760006103168286610b5a565b9050804710610381578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b50505050505b505b505b60008573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190610b94565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff808316602483015291925060009187169063dd62ed3e90604401602060405180830381865afa15801561046e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104929190610b41565b90508481101561062d576104be73ffffffffffffffffffffffffffffffffffffffff871683600061083c565b6104ff73ffffffffffffffffffffffffffffffffffffffff8716837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61083c565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301526000919088169063dd62ed3e90604401602060405180830381865afa158015610575573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105999190610b41565b90508581101561062b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c73000000000000000000000000000000000000000000006064820152608401610233565b505b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa15801561069a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106be9190610b41565b9050858110156107f75773ffffffffffffffffffffffffffffffffffffffff841663494666b6886106ef848a610b5a565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff90921660048301526024820152604401600060405180830381600087803b15801561075a57600080fd5b505af192505050801561076b575060015b6107f7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b5050505050505050565b60008061082f60017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610b5a565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052906000906108cf90861683610947565b90506108da8161095c565b610940576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c65640000000000006044820152606401610233565b5050505050565b606061095583600084610983565b9392505050565b600081516000148061097d57508180602001905181019061097d9190610bb1565b92915050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516109ad9190610b12565b60006040518083038185875af1925050503d80600081146109ea576040519150601f19603f3d011682016040523d82523d6000602084013e6109ef565b606091505b509250905080610a0157815160208301fd5b509392505050565b600080600060408486031215610a1e57600080fd5b83359250602084013567ffffffffffffffff80821115610a3d57600080fd5b818601915086601f830112610a5157600080fd5b813581811115610a6057600080fd5b876020828501011115610a7257600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610aa757600080fd5b50565b600080600080600060a08688031215610ac257600080fd5b8535610acd81610a85565b94506020860135610add81610a85565b9350604086013592506060860135610af481610a85565b91506080860135610b0481610a85565b809150509295509295909350565b6000825160005b81811015610b335760208186018101518583015201610b19565b506000920191825250919050565b600060208284031215610b5357600080fd5b5051919050565b8181038181111561097d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060208284031215610ba657600080fd5b815161095581610a85565b600060208284031215610bc357600080fd5b8151801515811461095557600080fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/solidity/Trader.sol b/crates/contracts/solidity/Trader.sol index fb86b5954d..08bf967a12 100644 --- a/crates/contracts/solidity/Trader.sol +++ b/crates/contracts/solidity/Trader.sol @@ -96,7 +96,8 @@ contract Trader { } } - uint256 currentAllowance = IERC20(sellToken).allowance(address(this), address(settlementContract.vaultRelayer())); + address vaultRelayer = settlementContract.vaultRelayer(); + uint256 currentAllowance = IERC20(sellToken).allowance(address(this), vaultRelayer); if (currentAllowance < sellAmount) { // Simulate an approval to the settlement contract so the user doesn't have to // spend gas on that just to get a quote. If they are happy with the quote and @@ -104,12 +105,11 @@ contract Trader { // We first reset the allowance to 0 since some ERC20 tokens (e.g. USDT) // require that due to this attack: // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - // We catch reverts because we'll later assert the correct approval got set anyway. - try IERC20(sellToken).approve(address(settlementContract.vaultRelayer()), 0) {} - catch {} - try IERC20(sellToken).approve(address(settlementContract.vaultRelayer()), type(uint256).max) {} - catch {} - uint256 allowance = IERC20(sellToken).allowance(address(this), address(settlementContract.vaultRelayer())); + // In order to handle tokens which are not ERC20 compliant (like USDT) we have + // to use `safeApprove()` instead of the regular `approve()` here. + IERC20(sellToken).safeApprove(vaultRelayer, 0); + IERC20(sellToken).safeApprove(vaultRelayer, type(uint256).max); + uint256 allowance = IERC20(sellToken).allowance(address(this), vaultRelayer); require(allowance >= sellAmount, "trader did not give the required approvals"); } diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index b189cc0d06..054aa2b3c6 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -11,6 +11,7 @@ use { number::nonzero::U256 as NonZeroU256, serde_json::json, shared::{ + addr, price_estimation::{ trade_verifier::{ balance_overrides::BalanceOverrides, @@ -62,6 +63,18 @@ async fn local_node_verified_quote_with_simulated_balance() { run_test(verified_quote_with_simulated_balance).await; } +#[tokio::test] +#[ignore] +async fn forked_node_mainnet_usdt_quote() { + run_forked_test_with_block_number( + usdt_quote_verification, + std::env::var("FORK_URL_MAINNET") + .expect("FORK_URL_MAINNET must be set to run forked tests"), + 21422760, + ) + .await; +} + /// Verified quotes work as expected. async fn standard_verified_quote(web3: Web3) { tracing::info!("Setting up chain state."); @@ -473,3 +486,41 @@ async fn verified_quote_with_simulated_balance(web3: Web3) { .unwrap(); assert!(response.verified); } + +/// Ensures that quotes can even be verified with tokens like `USDT` +/// which are not completely ERC20 compliant. +async fn usdt_quote_verification(web3: Web3) { + let mut onchain = OnchainComponents::deployed(web3.clone()).await; + + let [solver] = onchain.make_solvers_forked(to_wei(1)).await; + + let usdc = addr!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); + let usdt = addr!("dac17f958d2ee523a2206206994597c13d831ec7"); + + // Place Orders + let services = Services::new(&onchain).await; + services + .start_protocol_with_args( + ExtraServiceArgs { + api: vec!["--quote-autodetect-token-balance-overrides=true".to_string()], + ..Default::default() + }, + solver, + ) + .await; + + let quote = services + .submit_quote(&OrderQuoteRequest { + sell_token: usdt, + buy_token: usdc, + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::BeforeFee { + value: to_wei_with_exp(1000, 18).try_into().unwrap(), + }, + }, + ..Default::default() + }) + .await + .unwrap(); + assert!(quote.verified); +} From c93d3f6418924bedd9518d956f4ac124fea254fc Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 17 Dec 2024 16:10:51 +0000 Subject: [PATCH 21/25] [EASY] Simulate settlement tx before submitting (#3166) # Description On the `base` network, there are more settlement tx onchain reverts than expected. According to the logs, we couldn't catch those reverts using `estimate_gas` calls that happen only **after** the tx is submitted to the mempool. The tx also gets simulated before submitting a solution to the competition. However, the delay between that event and the actual execution can cause the tx to become invalid later. So, this PR adds additional `estimate_gas` call **before** the tx gets submitted. ## How to test Production logs. --- crates/driver/src/domain/mempools.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/driver/src/domain/mempools.rs b/crates/driver/src/domain/mempools.rs index 6200caaeb3..e5c73545a7 100644 --- a/crates/driver/src/domain/mempools.rs +++ b/crates/driver/src/domain/mempools.rs @@ -107,6 +107,24 @@ impl Mempools { let mut block_stream = into_stream(self.ethereum.current_block().clone()); block_stream.next().await; + // The tx is simulated before submitting the solution to the competition, but a + // delay between that and the actual execution can cause the simulation to be + // invalid which doesn't make sense to submit to the mempool anymore. + if let Err(err) = self.ethereum.estimate_gas(tx).await { + if err.is_revert() { + tracing::info!( + ?err, + "settlement tx simulation reverted before submitting to the mempool" + ); + return Err(Error::SimulationRevert); + } else { + tracing::warn!( + ?err, + "couldn't simulate tx before submitting to the mempool" + ); + } + } + let hash = mempool.submit(tx.clone(), settlement.gas, solver).await?; tracing::debug!(?hash, "submitted tx to the mempool"); From 3c8df6347b6fa26097b856ed7dfc3e21450faf3d Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Wed, 18 Dec 2024 12:03:17 +0100 Subject: [PATCH 22/25] [EASY] Add base url to open api (#3170) # Description We forgot to add `base` to the openapi spec so it doesn't show up in the network selection tab as reported by a user. # Changes adds the 2 new URLs to the openapi spec --- crates/orderbook/openapi.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/orderbook/openapi.yml b/crates/orderbook/openapi.yml index c26608a911..d470a9f24b 100644 --- a/crates/orderbook/openapi.yml +++ b/crates/orderbook/openapi.yml @@ -15,6 +15,10 @@ servers: url: "https://api.cow.fi/arbitrum_one" - description: Arbitrum One (Staging) url: "https://barn.api.cow.fi/arbitrum_one" + - description: Base (Prod) + url: "https://api.cow.fi/base" + - description: Base (Staging) + url: "https://barn.api.cow.fi/base" - description: Sepolia (Prod) url: "https://api.cow.fi/sepolia" - description: Sepolia (Staging) From bf213b9734a60a05c9c11fd776ce1868439193c5 Mon Sep 17 00:00:00 2001 From: Dusan Stanivukovic Date: Thu, 19 Dec 2024 09:08:01 +0100 Subject: [PATCH 23/25] Fix solver competition reading (#3171) # Description Fixes a bug where transaction hashes from different environment (prod/staging) are shown on solver competition endpoint. Example: https://api.cow.fi/mainnet/api/v1/solver_competition/9861815 Here, transaction `0xb608f1e2a644befda65a6a87ce6b93fc8a3441d9729b8ce4b2a2db02c1247e60` is from production enviroment and valid one, while `0xc85cd0ad8e3aac389836a71da9795bdd1bedce7b21da1950b48f93165c9f6be8` is from staging environment and mistakenly added to result. The reason why this happens is: 1. The solver competition with the same `auction_id` can be saved in both environments, regardless if the competition ended up with mined settlement (which is fine). 2. `settlement::Observer` saves the relationship even for settlements from another environment (this is a decision made long time ago, before refactoring, [reference code here](https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/infra/persistence/mod.rs#L654-L660) ) But, what this PR introduces is leveraging the fact that, for settlement from another environment, there is no matching entry in `settlement_observations` table, [reference code here](https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/infra/persistence/mod.rs#L682-L693). Note how the saving is executed only if the `settlement` exists, which if [false](https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/domain/settlement/observer.rs#L90-L92) for settlements from another environment. # Changes - [ ] Update reading functions for solver competition, to exclude transaction hashes for settlements from another environment. ## How to test Updated unit test. Existing e2e tests. Manually tested all updated functions on production database, works as expected. --- crates/database/src/solver_competition.rs | 65 +++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/crates/database/src/solver_competition.rs b/crates/database/src/solver_competition.rs index 55319a8e5e..01e0183858 100644 --- a/crates/database/src/solver_competition.rs +++ b/crates/database/src/solver_competition.rs @@ -38,10 +38,14 @@ pub async fn load_by_id( id: AuctionId, ) -> Result, sqlx::Error> { const QUERY: &str = r#" -SELECT sc.json, sc.id, COALESCE(ARRAY_AGG(s.tx_hash) FILTER (WHERE s.tx_hash IS NOT NULL), '{}') AS tx_hashes +SELECT sc.json, sc.id, COALESCE(ARRAY_AGG(s.tx_hash) FILTER (WHERE so.block_number IS NOT NULL), '{}') AS tx_hashes FROM solver_competitions sc -- outer joins because the data might not have been indexed yet LEFT OUTER JOIN settlements s ON sc.id = s.auction_id +-- exclude settlements from another environment for which observation is guaranteed to not exist +LEFT OUTER JOIN settlement_observations so + ON s.block_number = so.block_number + AND s.log_index = so.log_index WHERE sc.id = $1 GROUP BY sc.id ;"#; @@ -52,10 +56,14 @@ pub async fn load_latest_competition( ex: &mut PgConnection, ) -> Result, sqlx::Error> { const QUERY: &str = r#" -SELECT sc.json, sc.id, COALESCE(ARRAY_AGG(s.tx_hash) FILTER (WHERE s.tx_hash IS NOT NULL), '{}') AS tx_hashes +SELECT sc.json, sc.id, COALESCE(ARRAY_AGG(s.tx_hash) FILTER (WHERE so.block_number IS NOT NULL), '{}') AS tx_hashes FROM solver_competitions sc -- outer joins because the data might not have been indexed yet LEFT OUTER JOIN settlements s ON sc.id = s.auction_id +-- exclude settlements from another environment for which observation is guaranteed to not exist +LEFT OUTER JOIN settlement_observations so + ON s.block_number = so.block_number + AND s.log_index = so.log_index GROUP BY sc.id ORDER BY sc.id DESC LIMIT 1 @@ -72,11 +80,17 @@ WITH competition AS ( SELECT sc.id FROM solver_competitions sc JOIN settlements s ON sc.id = s.auction_id + JOIN settlement_observations so + ON s.block_number = so.block_number + AND s.log_index = so.log_index WHERE s.tx_hash = $1 ) -SELECT sc.json, sc.id, COALESCE(ARRAY_AGG(s.tx_hash) FILTER (WHERE s.tx_hash IS NOT NULL), '{}') AS tx_hashes +SELECT sc.json, sc.id, COALESCE(ARRAY_AGG(s.tx_hash) FILTER (WHERE so.block_number IS NOT NULL), '{}') AS tx_hashes FROM solver_competitions sc JOIN settlements s ON sc.id = s.auction_id +JOIN settlement_observations so + ON s.block_number = so.block_number + AND s.log_index = so.log_index WHERE sc.id = (SELECT id FROM competition) GROUP BY sc.id ;"#; @@ -336,7 +350,9 @@ mod tests { // non-existent auction returns none assert!(load_by_id(&mut db, 1).await.unwrap().is_none()); - // insert two settlement events for the same auction id + // insert three settlement events for the same auction id, with one of them not + // having observation (in practice usually meaning it's from different + // environment) crate::events::insert_settlement( &mut db, &EventIndex { @@ -350,6 +366,16 @@ mod tests { ) .await .unwrap(); + crate::settlement_observations::upsert( + &mut db, + crate::settlement_observations::Observation { + block_number: 0, + log_index: 0, + ..Default::default() + }, + ) + .await + .unwrap(); crate::events::insert_settlement( &mut db, &EventIndex { @@ -363,12 +389,38 @@ mod tests { ) .await .unwrap(); + crate::settlement_observations::upsert( + &mut db, + crate::settlement_observations::Observation { + block_number: 0, + log_index: 1, + ..Default::default() + }, + ) + .await + .unwrap(); + crate::events::insert_settlement( + &mut db, + &EventIndex { + block_number: 0, + log_index: 2, + }, + &Settlement { + solver: Default::default(), + transaction_hash: ByteArray([2u8; 32]), + }, + ) + .await + .unwrap(); crate::settlements::update_settlement_auction(&mut db, 0, 0, 0) .await .unwrap(); crate::settlements::update_settlement_auction(&mut db, 0, 1, 0) .await .unwrap(); + crate::settlements::update_settlement_auction(&mut db, 0, 2, 0) + .await + .unwrap(); // load by id works, and finds two hashes let value_ = load_by_id(&mut db, 0).await.unwrap().unwrap(); @@ -389,6 +441,11 @@ mod tests { .unwrap() .unwrap(); assert!(value_.tx_hashes.len() == 2); + // this one should not find any hashes since it's from another environment + let value_ = load_by_tx_hash(&mut db, &ByteArray([2u8; 32])) + .await + .unwrap(); + assert!(value_.is_none()); } #[tokio::test] From fe99739879a2c3ffddda88f6071117bc12c97cc1 Mon Sep 17 00:00:00 2001 From: Dusan Stanivukovic Date: Thu, 19 Dec 2024 09:57:01 +0100 Subject: [PATCH 24/25] Remove "latest_settlement_block" from API (#3151) # Description Recently we introduced a new database table to store all auction objects -> `competition_auctions` table. Note that this table does not contain field `latest_settlement_block`. The reason is that this field is actually deductible from other data in the database -> it's even deductible from blockchain. For a given block X, you can always know what is the latest block before block X where the last settlement was settled. Moreover, `latest_settlement_block` is now only used internally in solvable orders cache component of autopilot, so it's not actually a core autopilot information. This change opens the door to unify `auction` and `competition_auctions` table with final goal of removing `auction` table completely. Removes tech debt. # Changes - [ ] Remove `latest_settlement_block` field from api and autopilot ## How to test All tests passing. If you are worried about a transition period, removing a field shouldn't be problematic for deserializing old object containing `latest_settlement_block`. The only concern is that solvers needs to adjust their deserialize logic so it doesn't break. Will be notified in advance once approvals are collected. --- crates/autopilot/src/domain/auction/mod.rs | 3 --- crates/autopilot/src/infra/persistence/dto/auction.rs | 3 --- crates/autopilot/src/run_loop.rs | 1 - crates/autopilot/src/solvable_orders.rs | 1 - crates/model/src/auction.rs | 11 ----------- crates/model/src/lib.rs | 7 ------- crates/orderbook/openapi.yml | 8 -------- crates/orderbook/src/database/orders.rs | 5 ----- crates/orderbook/src/dto/auction.rs | 1 - 9 files changed, 40 deletions(-) diff --git a/crates/autopilot/src/domain/auction/mod.rs b/crates/autopilot/src/domain/auction/mod.rs index 3b0bd587c3..8906bad418 100644 --- a/crates/autopilot/src/domain/auction/mod.rs +++ b/crates/autopilot/src/domain/auction/mod.rs @@ -13,7 +13,6 @@ pub mod order; #[derive(Clone, Debug, PartialEq)] pub struct RawAuctionData { pub block: u64, - pub latest_settlement_block: u64, pub orders: Vec, pub prices: Prices, pub surplus_capturing_jit_order_owners: Vec, @@ -25,7 +24,6 @@ pub type Id = i64; pub struct Auction { pub id: Id, pub block: u64, - pub latest_settlement_block: u64, pub orders: Vec, pub prices: Prices, pub surplus_capturing_jit_order_owners: Vec, @@ -34,7 +32,6 @@ pub struct Auction { impl PartialEq for Auction { fn eq(&self, other: &Self) -> bool { self.block == other.block - && self.latest_settlement_block == other.latest_settlement_block && self.orders == other.orders && self.prices == other.prices && self.surplus_capturing_jit_order_owners == other.surplus_capturing_jit_order_owners diff --git a/crates/autopilot/src/infra/persistence/dto/auction.rs b/crates/autopilot/src/infra/persistence/dto/auction.rs index 0ed2f3588a..3c576a74fc 100644 --- a/crates/autopilot/src/infra/persistence/dto/auction.rs +++ b/crates/autopilot/src/infra/persistence/dto/auction.rs @@ -14,7 +14,6 @@ use { pub fn from_domain(auction: domain::RawAuctionData) -> RawAuctionData { RawAuctionData { block: auction.block, - latest_settlement_block: auction.latest_settlement_block, orders: auction .orders .into_iter() @@ -38,7 +37,6 @@ pub fn from_domain(auction: domain::RawAuctionData) -> RawAuctionData { #[serde(rename_all = "camelCase")] pub struct RawAuctionData { pub block: u64, - pub latest_settlement_block: u64, pub orders: Vec, #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub prices: BTreeMap, @@ -62,7 +60,6 @@ impl Auction { Ok(domain::Auction { id: self.id, block: self.auction.block, - latest_settlement_block: self.auction.latest_settlement_block, orders: self .auction .orders diff --git a/crates/autopilot/src/run_loop.rs b/crates/autopilot/src/run_loop.rs index e86692ff4e..99b35f3a09 100644 --- a/crates/autopilot/src/run_loop.rs +++ b/crates/autopilot/src/run_loop.rs @@ -214,7 +214,6 @@ impl RunLoop { Some(domain::Auction { id, block: auction.block, - latest_settlement_block: auction.latest_settlement_block, orders: auction.orders, prices: auction.prices, surplus_capturing_jit_order_owners: auction.surplus_capturing_jit_order_owners, diff --git a/crates/autopilot/src/solvable_orders.rs b/crates/autopilot/src/solvable_orders.rs index 9c23b39868..25155833cd 100644 --- a/crates/autopilot/src/solvable_orders.rs +++ b/crates/autopilot/src/solvable_orders.rs @@ -267,7 +267,6 @@ impl SolvableOrdersCache { .collect::>(); let auction = domain::RawAuctionData { block, - latest_settlement_block: db_solvable_orders.latest_settlement_block, orders: orders .into_iter() .map(|order| { diff --git a/crates/model/src/auction.rs b/crates/model/src/auction.rs index 94cd04aa44..f0dd8a434a 100644 --- a/crates/model/src/auction.rs +++ b/crates/model/src/auction.rs @@ -34,15 +34,6 @@ pub struct Auction { /// The block number for the auction. Orders and prices are guaranteed to be /// valid on this block. pub block: u64, - - /// The latest block on which a settlement has been processed. This field is - /// used to tell which orders are still in-flight. See - /// [`InFlightOrders`]. - /// - /// Note that under certain conditions it is possible for a settlement to - /// have been mined as part of [`block`] but not have yet been processed. - pub latest_settlement_block: u64, - /// The solvable orders included in the auction. pub orders: Vec, @@ -72,7 +63,6 @@ mod tests { }; let auction = Auction { block: 42, - latest_settlement_block: 40, orders: vec![order(1), order(2)], prices: btreemap! { H160([2; 20]) => U256::from(2), @@ -86,7 +76,6 @@ mod tests { json!({ "id": 0, "block": 42, - "latestSettlementBlock": 40, "orders": [ order(1), order(2), diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index 23a13d7729..044bb67bf5 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -139,13 +139,6 @@ impl DomainSeparator { } } -#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct SolvableOrders { - pub orders: Vec, - pub latest_settlement_block: u64, -} - #[cfg(test)] mod tests { use { diff --git a/crates/orderbook/openapi.yml b/crates/orderbook/openapi.yml index d470a9f24b..18e48c405f 100644 --- a/crates/orderbook/openapi.yml +++ b/crates/orderbook/openapi.yml @@ -1103,14 +1103,6 @@ components: The block number for the auction. Orders and prices are guaranteed to be valid on this block. Proposed settlements should be valid for this block as well. - latestSettlementBlock: - type: integer - description: > - The latest block on which a settlement has been processed. - - **NOTE**: Under certain conditions it is possible for a settlement - to have been mined as part of `block` but not have yet been - processed. orders: type: array items: diff --git a/crates/orderbook/src/database/orders.rs b/crates/orderbook/src/database/orders.rs index a21884f9c1..64bde48916 100644 --- a/crates/orderbook/src/database/orders.rs +++ b/crates/orderbook/src/database/orders.rs @@ -80,11 +80,6 @@ pub trait OrderStoring: Send + Sync { async fn single_order_with_quote(&self, uid: &OrderUid) -> Result>; } -pub struct SolvableOrders { - pub orders: Vec, - pub latest_settlement_block: u64, -} - pub struct OrderWithQuote { pub order: Order, pub quote: Option, diff --git a/crates/orderbook/src/dto/auction.rs b/crates/orderbook/src/dto/auction.rs index a132bbf747..fe3d237fbf 100644 --- a/crates/orderbook/src/dto/auction.rs +++ b/crates/orderbook/src/dto/auction.rs @@ -13,7 +13,6 @@ use { #[serde(rename_all = "camelCase")] pub struct Auction { pub block: u64, - pub latest_settlement_block: u64, pub orders: Vec, #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub prices: BTreeMap, From 796e7bd01e63ec384611168446e00d4473f54208 Mon Sep 17 00:00:00 2001 From: Dusan Stanivukovic Date: Thu, 19 Dec 2024 10:58:12 +0100 Subject: [PATCH 25/25] Stop using string for solution_id (#3154) # Description Follow up for https://github.com/cowprotocol/services/pull/3152, and a final step to switch to using INTEGER for solution id only. Will be merged on Friday, once all solvers confirm they updated their drivers to include https://github.com/cowprotocol/services/pull/3152. OPENAPI is already up to date. Fixes https://github.com/cowprotocol/services/issues/3072 # Changes - [ ] Stop accepting string type for solution id ## How to test e2e tests --- .../autopilot/src/infra/solvers/dto/reveal.rs | 1 - .../autopilot/src/infra/solvers/dto/settle.rs | 1 - .../autopilot/src/infra/solvers/dto/solve.rs | 32 ----------------- crates/driver/src/infra/api/routes/mod.rs | 31 ---------------- .../api/routes/reveal/dto/reveal_request.rs | 3 +- .../api/routes/settle/dto/settle_request.rs | 3 +- crates/driver/src/tests/cases/buy_eth.rs | 2 +- .../src/tests/cases/merge_settlements.rs | 12 +++---- .../src/tests/cases/multiple_solutions.rs | 4 +-- .../src/tests/cases/parallel_auctions.rs | 35 ++++++++----------- crates/driver/src/tests/cases/settle.rs | 8 ++--- .../driver/src/tests/cases/solver_balance.rs | 2 +- crates/driver/src/tests/setup/driver.rs | 4 +-- crates/driver/src/tests/setup/mod.rs | 9 +++-- 14 files changed, 37 insertions(+), 110 deletions(-) diff --git a/crates/autopilot/src/infra/solvers/dto/reveal.rs b/crates/autopilot/src/infra/solvers/dto/reveal.rs index f963fce1a5..41971d28b1 100644 --- a/crates/autopilot/src/infra/solvers/dto/reveal.rs +++ b/crates/autopilot/src/infra/solvers/dto/reveal.rs @@ -9,7 +9,6 @@ use { #[serde(rename_all = "camelCase")] pub struct Request { /// Unique ID of the solution (per driver competition), to reveal. - #[serde_as(as = "serde_with::DisplayFromStr")] pub solution_id: u64, /// Auction ID in which the specified solution ID is competing. #[serde_as(as = "Option")] diff --git a/crates/autopilot/src/infra/solvers/dto/settle.rs b/crates/autopilot/src/infra/solvers/dto/settle.rs index 3abdf15dbe..5277eef76e 100644 --- a/crates/autopilot/src/infra/solvers/dto/settle.rs +++ b/crates/autopilot/src/infra/solvers/dto/settle.rs @@ -9,7 +9,6 @@ use { #[serde(rename_all = "camelCase")] pub struct Request { /// Unique ID of the solution (per driver competition), to settle. - #[serde_as(as = "serde_with::DisplayFromStr")] pub solution_id: u64, /// The last block number in which the solution TX can be included pub submission_deadline_latest_block: u64, diff --git a/crates/autopilot/src/infra/solvers/dto/solve.rs b/crates/autopilot/src/infra/solvers/dto/solve.rs index a54c143f70..ad793a7f2f 100644 --- a/crates/autopilot/src/infra/solvers/dto/solve.rs +++ b/crates/autopilot/src/infra/solvers/dto/solve.rs @@ -170,7 +170,6 @@ pub enum Side { pub struct Solution { /// Unique ID of the solution (per driver competition), used to identify /// it in subsequent requests (reveal, settle). - #[serde(deserialize_with = "deserialize_solution_id")] pub solution_id: u64, #[serde_as(as = "HexOrDecimalU256")] pub score: U256, @@ -182,37 +181,6 @@ pub struct Solution { pub gas: Option, } -fn deserialize_solution_id<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - struct SolutionIdVisitor; - - impl serde::de::Visitor<'_> for SolutionIdVisitor { - type Value = u64; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a string or integer representing a solution ID") - } - - fn visit_u64(self, value: u64) -> Result - where - E: serde::de::Error, - { - Ok(value) - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - value.parse::().map_err(serde::de::Error::custom) - } - } - - deserializer.deserialize_any(SolutionIdVisitor) -} - #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Response { diff --git a/crates/driver/src/infra/api/routes/mod.rs b/crates/driver/src/infra/api/routes/mod.rs index 434846385e..ee1027b0e9 100644 --- a/crates/driver/src/infra/api/routes/mod.rs +++ b/crates/driver/src/infra/api/routes/mod.rs @@ -15,34 +15,3 @@ pub(super) use { settle::settle, solve::{solve, AuctionError}, }; - -pub(crate) fn deserialize_solution_id<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - struct SolutionIdVisitor; - - impl serde::de::Visitor<'_> for SolutionIdVisitor { - type Value = u64; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a string or integer representing a solution ID") - } - - fn visit_u64(self, value: u64) -> Result - where - E: serde::de::Error, - { - Ok(value) - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - value.parse::().map_err(serde::de::Error::custom) - } - } - - deserializer.deserialize_any(SolutionIdVisitor) -} diff --git a/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs b/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs index f298b55700..c782ed57fc 100644 --- a/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs +++ b/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs @@ -1,11 +1,10 @@ -use {super::super::super::deserialize_solution_id, serde::Deserialize, serde_with::serde_as}; +use {serde::Deserialize, serde_with::serde_as}; #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RevealRequest { /// Unique ID of the solution (per driver competition), to reveal. - #[serde(deserialize_with = "deserialize_solution_id")] pub solution_id: u64, /// Auction ID in which the specified solution ID is competing. #[serde_as(as = "Option")] diff --git a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs index 478d0766f2..844a716738 100644 --- a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs +++ b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs @@ -1,11 +1,10 @@ -use {super::super::super::deserialize_solution_id, serde::Deserialize, serde_with::serde_as}; +use {serde::Deserialize, serde_with::serde_as}; #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SettleRequest { /// Unique ID of the solution (per driver competition), to settle. - #[serde(deserialize_with = "deserialize_solution_id")] pub solution_id: u64, /// The last block number in which the solution TX can be included pub submission_deadline_latest_block: u64, diff --git a/crates/driver/src/tests/cases/buy_eth.rs b/crates/driver/src/tests/cases/buy_eth.rs index 204dc7b2bd..35bf50f699 100644 --- a/crates/driver/src/tests/cases/buy_eth.rs +++ b/crates/driver/src/tests/cases/buy_eth.rs @@ -16,5 +16,5 @@ async fn test() { .await; let id = test.solve().await.ok().orders(&[order]).id(); - test.settle(&id).await.ok().await.eth_order_executed().await; + test.settle(id).await.ok().await.eth_order_executed().await; } diff --git a/crates/driver/src/tests/cases/merge_settlements.rs b/crates/driver/src/tests/cases/merge_settlements.rs index ae27ac6f4b..8c16ecaa6f 100644 --- a/crates/driver/src/tests/cases/merge_settlements.rs +++ b/crates/driver/src/tests/cases/merge_settlements.rs @@ -35,8 +35,8 @@ async fn possible() { .await; let id = test.solve().await.ok().orders(&[ab_order, cd_order]).id(); - test.reveal(&id).await.ok().calldata(); - test.settle(&id) + test.reveal(id).await.ok().calldata(); + test.settle(id) .await // Even though the solver returned two solutions, the executed settlement is a // combination of the two, meaning the settlements were merged successfully. @@ -91,8 +91,8 @@ async fn impossible() { // Only the first A-B order gets settled. let id = test.solve().await.ok().orders(&[order]).id(); - test.reveal(&id).await.ok().calldata(); - test.settle(&id).await.ok().await.ab_order_executed().await; + test.reveal(id).await.ok().calldata(); + test.settle(id).await.ok().await.ab_order_executed().await; } /// Test that mergable solutions don't get merged if feature was not enabled. @@ -114,6 +114,6 @@ async fn possible_but_forbidden() { // Even though the solutions could be combined (see test "possible") they were // not because solution merging is not enabled by default. let id = test.solve().await.ok().orders(&[ab_order]).id(); - test.reveal(&id).await.ok().calldata(); - test.settle(&id).await.ok().await.ab_order_executed().await; + test.reveal(id).await.ok().calldata(); + test.settle(id).await.ok().await.ab_order_executed().await; } diff --git a/crates/driver/src/tests/cases/multiple_solutions.rs b/crates/driver/src/tests/cases/multiple_solutions.rs index f5da54e33b..33c78efd5d 100644 --- a/crates/driver/src/tests/cases/multiple_solutions.rs +++ b/crates/driver/src/tests/cases/multiple_solutions.rs @@ -18,7 +18,7 @@ async fn valid() { .await; let id = test.solve().await.ok().orders(&[order]).id(); - test.reveal(&id).await.ok().calldata(); + test.reveal(id).await.ok().calldata(); } /// Test that the invalid solution is discarded when the /solve endpoint @@ -36,5 +36,5 @@ async fn invalid() { .await; let id = test.solve().await.ok().orders(&[order]).id(); - test.reveal(&id).await.ok().calldata(); + test.reveal(id).await.ok().calldata(); } diff --git a/crates/driver/src/tests/cases/parallel_auctions.rs b/crates/driver/src/tests/cases/parallel_auctions.rs index 12445d2f8b..cac7e3f186 100644 --- a/crates/driver/src/tests/cases/parallel_auctions.rs +++ b/crates/driver/src/tests/cases/parallel_auctions.rs @@ -25,25 +25,25 @@ async fn driver_handles_solutions_based_on_id() { // calling `/reveal` or `/settle` with incorrect solution ids // results in an error. - test.settle("99").await.err().kind("SolutionNotAvailable"); - test.reveal("99").await.err().kind("SolutionNotAvailable"); + test.settle(99).await.err().kind("SolutionNotAvailable"); + test.reveal(99).await.err().kind("SolutionNotAvailable"); // calling `/reveal` or `/settle` with a reasonable id works // but wrong auction id results in an error. test.set_auction_id(100); - test.reveal(&solution_id) + test.reveal(solution_id) .await .err() .kind("SolutionNotAvailable"); - test.settle(&solution_id) + test.settle(solution_id) .await .err() .kind("SolutionNotAvailable"); // calling `/reveal` or `/settle` with a reasonable id works. test.set_auction_id(1); - test.reveal(&solution_id).await.ok(); - test.settle(&solution_id) + test.reveal(solution_id).await.ok(); + test.settle(solution_id) .await .ok() .await @@ -52,11 +52,11 @@ async fn driver_handles_solutions_based_on_id() { // calling `/reveal` or `/settle` with for a legit solution that // has already been settled also fails. - test.settle(&solution_id) + test.settle(solution_id) .await .err() .kind("SolutionNotAvailable"); - test.reveal(&solution_id) + test.reveal(solution_id) .await .err() .kind("SolutionNotAvailable"); @@ -89,12 +89,7 @@ async fn driver_can_settle_old_solutions() { // Technically this is not super convincing since all remembered solutions // are identical but this is the best we are going to get without needing // to heavily modify the testing framework. - test.settle(&id1) - .await - .ok() - .await - .eth_order_executed() - .await; + test.settle(id1).await.ok().await.eth_order_executed().await; } /// Tests that the driver only remembers a relatively small number of solutions. @@ -118,12 +113,12 @@ async fn driver_has_a_short_memory() { let id6 = test.solve().await.ok().id(); // recalling the 5 most recent solutions works - test.reveal(&id2).await.ok(); - test.reveal(&id3).await.ok(); - test.reveal(&id4).await.ok(); - test.reveal(&id5).await.ok(); - test.reveal(&id6).await.ok(); + test.reveal(id2).await.ok(); + test.reveal(id3).await.ok(); + test.reveal(id4).await.ok(); + test.reveal(id5).await.ok(); + test.reveal(id6).await.ok(); // recalling an older solution doesn't work - test.reveal(&id1).await.err().kind("SolutionNotAvailable"); + test.reveal(id1).await.err().kind("SolutionNotAvailable"); } diff --git a/crates/driver/src/tests/cases/settle.rs b/crates/driver/src/tests/cases/settle.rs index 110d138734..fd50e1779c 100644 --- a/crates/driver/src/tests/cases/settle.rs +++ b/crates/driver/src/tests/cases/settle.rs @@ -30,7 +30,7 @@ async fn matrix() { .await; let id = test.solve().await.ok().id(); - test.settle(&id).await.ok().await.ab_order_executed().await; + test.settle(id).await.ok().await.ab_order_executed().await; } } } @@ -47,7 +47,7 @@ async fn solution_not_available() { .done() .await; - test.settle("99").await.err().kind("SolutionNotAvailable"); + test.settle(99).await.err().kind("SolutionNotAvailable"); } /// Checks that settlements with revert risk are not submitted via public @@ -71,7 +71,7 @@ async fn private_rpc_with_high_risk_solution() { let id = test.solve().await.ok().id(); // Public cannot be used and private RPC is not available - test.settle(&id).await.err().kind("FailedToSubmit"); + test.settle(id).await.err().kind("FailedToSubmit"); } #[tokio::test] @@ -108,5 +108,5 @@ async fn high_gas_limit() { .execute("evm_setBlockGasLimit", vec![serde_json::json!(9_000_000)]) .await .unwrap(); - test.settle(&id).await.ok().await; + test.settle(id).await.ok().await; } diff --git a/crates/driver/src/tests/cases/solver_balance.rs b/crates/driver/src/tests/cases/solver_balance.rs index 04a12b4408..3294d3405e 100644 --- a/crates/driver/src/tests/cases/solver_balance.rs +++ b/crates/driver/src/tests/cases/solver_balance.rs @@ -42,7 +42,7 @@ async fn test_just_enough_funded() { .await; let id = test.solve_with_solver("barely_funded").await.ok().id(); - test.settle_with_solver("barely_funded", &id) + test.settle_with_solver("barely_funded", id) .await .ok() .await; diff --git a/crates/driver/src/tests/setup/driver.rs b/crates/driver/src/tests/setup/driver.rs index 6cf08b1cc6..cd7115fe38 100644 --- a/crates/driver/src/tests/setup/driver.rs +++ b/crates/driver/src/tests/setup/driver.rs @@ -147,7 +147,7 @@ pub fn solve_req(test: &Test) -> serde_json::Value { } /// Create a request for the driver /reveal endpoint. -pub fn reveal_req(solution_id: &str, auction_id: &str) -> serde_json::Value { +pub fn reveal_req(solution_id: u64, auction_id: &str) -> serde_json::Value { json!({ "solutionId": solution_id, "auctionId": auction_id, @@ -157,7 +157,7 @@ pub fn reveal_req(solution_id: &str, auction_id: &str) -> serde_json::Value { /// Create a request for the driver /settle endpoint. pub fn settle_req( submission_deadline_latest_block: u64, - solution_id: &str, + solution_id: u64, auction_id: &str, ) -> serde_json::Value { json!({ diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index 171ebc6261..dc22169e29 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -1039,7 +1039,7 @@ impl Test { } /// Call the /reveal endpoint. - pub async fn reveal(&self, solution_id: &str) -> Reveal { + pub async fn reveal(&self, solution_id: u64) -> Reveal { let res = self .client .post(format!( @@ -1089,11 +1089,11 @@ impl Test { } /// Call the /settle endpoint. - pub async fn settle(&self, solution_id: &str) -> Settle { + pub async fn settle(&self, solution_id: u64) -> Settle { self.settle_with_solver(solver::NAME, solution_id).await } - pub async fn settle_with_solver(&self, solver_name: &str, solution_id: &str) -> Settle { + pub async fn settle_with_solver(&self, solver_name: &str, solution_id: u64) -> Settle { /// The maximum number of blocks to wait for a settlement to appear on /// chain. const SUBMISSION_DEADLINE: u64 = 3; @@ -1213,13 +1213,12 @@ impl SolveOk<'_> { /// Extracts the solution id from the response. Since response can contain /// multiple solutions, it takes the id from the first solution. - pub fn id(&self) -> String { + pub fn id(&self) -> u64 { let solution = self.solution(); solution .get("solutionId") .unwrap() .as_u64() - .map(|id| id.to_string()) .unwrap() .to_owned() }