-
Notifications
You must be signed in to change notification settings - Fork 0
feat: use new wrapper from upstream #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5ebd847
b168d45
e56eac3
2e7f051
de12e99
c3dc655
217af04
4f6c81e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| +1 −1 | .github/CODEOWNERS | |
| +6 −0 | .github/dependabot.yml | |
| +68 −8 | .github/workflows/ci.yml | |
| +6 −1 | .github/workflows/sync.yml | |
| +12 −0 | RELEASE_CHECKLIST.md | |
| +8 −4 | foundry.toml | |
| +6 −0 | src/Base.sol | |
| +60 −0 | src/Config.sol | |
| +477 −0 | src/LibVariable.sol | |
| +1 −0 | src/StdChains.sol | |
| +612 −0 | src/StdConfig.sol | |
| +38 −12 | src/Vm.sol | |
| +352 −0 | test/Config.t.sol | |
| +434 −0 | test/LibVariable.t.sol | |
| +1 −1 | test/StdChains.t.sol | |
| +1 −1 | test/Vm.t.sol | |
| +81 −0 | test/fixtures/config.toml |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,18 +6,19 @@ import {IGPv2Settlement, GPv2Interaction} from "./vendor/interfaces/IGPv2Settlem | |
| import {IGPv2Authentication} from "./vendor/interfaces/IGPv2Authentication.sol"; | ||
|
|
||
| import {GPv2Signing, IERC20, GPv2Trade} from "cow/mixins/GPv2Signing.sol"; | ||
| import {GPv2Wrapper} from "cow/GPv2Wrapper.sol"; | ||
|
|
||
| import {SwapVerifier} from "./SwapVerifier.sol"; | ||
|
|
||
| import "forge-std/console.sol"; | ||
|
|
||
| /// @title CowEvcWrapper | ||
| /// @notice A wrapper around the EVC that allows for settlement operations | ||
| contract CowEvcWrapper is GPv2Signing, SwapVerifier { | ||
| contract CowEvcWrapper is GPv2Wrapper, GPv2Signing, SwapVerifier { | ||
| IEVC public immutable EVC; | ||
| IGPv2Settlement public immutable SETTLEMENT; | ||
|
|
||
| address public transient origSender; | ||
| /// @notice 0 = not executing, 1 = wrappedSettle() called and not yet internal settle, 2 = evcInternalSettle() called | ||
| uint256 public transient settleState; | ||
|
|
||
| error Unauthorized(address msgSender); | ||
| error NoReentrancy(); | ||
|
|
@@ -27,9 +28,10 @@ contract CowEvcWrapper is GPv2Signing, SwapVerifier { | |
|
|
||
| error NotEVCSettlement(); | ||
|
|
||
| constructor(address _evc, address payable _settlement) { | ||
| constructor(address _evc, address payable _settlement) | ||
| GPv2Wrapper(_settlement) | ||
| { | ||
| EVC = IEVC(_evc); | ||
| SETTLEMENT = IGPv2Settlement(_settlement); | ||
| } | ||
|
|
||
| struct ResolvedValues { | ||
|
|
@@ -38,117 +40,68 @@ contract CowEvcWrapper is GPv2Signing, SwapVerifier { | |
| uint256 minAmount; | ||
| } | ||
|
|
||
| /// @notice Specifies the EVC calls that will need to be executed | ||
| /// around a GPv2Settlement | ||
| /// call prior to `settle` call on this contract | ||
| /// @param preItems Items to execute before settlement | ||
| /// @param postItems Items to execute after settlement | ||
| function setEvcCalls(IEVC.BatchItem[] calldata preItems, IEVC.BatchItem[] calldata postItems) external { | ||
| _copyToTransientStorage(preItems, keccak256("preSettlementItems")); | ||
| _copyToTransientStorage(postItems, keccak256("postSettlementItems")); | ||
| } | ||
|
|
||
| /// @notice Executes a batch of EVC operations with a settlement in between | ||
| /// @notice Implementation of GPv2Wrapper._wrap - executes EVC operations around settlement | ||
| /// @param tokens Tokens involved in settlement | ||
| /// @param clearingPrices Clearing prices for settlement | ||
| /// @param trades Trade data for settlement | ||
| /// @param interactions Interaction data for settlement | ||
| function settle( | ||
| /// @param wrapperData Additional data for the wrapper (unused in this implementation) | ||
| function _wrap( | ||
| IERC20[] calldata tokens, | ||
| uint256[] calldata clearingPrices, | ||
| GPv2Trade.Data[] calldata trades, | ||
| GPv2Interaction.Data[][3] calldata interactions | ||
| ) external payable { | ||
| GPv2Interaction.Data[][3] calldata interactions, | ||
| bytes calldata wrapperData | ||
| ) internal override { | ||
| // prevent reentrancy: there is no reason why we would want to allow it here | ||
| if (origSender != address(0)) { | ||
| if (settleState != 0) { | ||
| revert NoReentrancy(); | ||
| } | ||
| origSender = msg.sender; | ||
| settleState = 1; | ||
|
|
||
| // Create a single batch with all items | ||
| IEVC.BatchItem[] memory preSettlementItems = _readFromTransientStorage(keccak256("preSettlementItems")); | ||
| IEVC.BatchItem[] memory postSettlementItems = _readFromTransientStorage(keccak256("postSettlementItems")); | ||
| // Decode wrapperData into pre and post settlement actions | ||
| (IEVC.BatchItem[] memory preSettlementItems, IEVC.BatchItem[] memory postSettlementItems) = | ||
| abi.decode(wrapperData, (IEVC.BatchItem[], IEVC.BatchItem[])); | ||
| IEVC.BatchItem[] memory items = new IEVC.BatchItem[](preSettlementItems.length + postSettlementItems.length + 1); | ||
|
|
||
| // Copy pre-settlement items | ||
| for (uint256 i = 0; i < preSettlementItems.length; i++) { | ||
| items[i] = preSettlementItems[i]; | ||
| uint256 ptr; | ||
| /*uint256 ptr; | ||
| uint256 ptr2; | ||
| IEVC.BatchItem memory subItem = preSettlementItems[i]; | ||
| bytes memory itemsData = preSettlementItems[i].data; | ||
| assembly { | ||
| ptr := itemsData | ||
| ptr2 := subItem | ||
| } | ||
| }*/ | ||
| } | ||
|
|
||
| // Add settlement call to wrapper | ||
| // Add settlement call to wrapper - use _internalSettle from GPv2Wrapper | ||
| items[preSettlementItems.length] = IEVC.BatchItem({ | ||
| onBehalfOfAccount: msg.sender, | ||
| targetContract: address(this), | ||
| value: 0, | ||
| data: abi.encodeCall(this.internalSettle, (tokens, clearingPrices, trades, interactions)) | ||
| data: abi.encodeCall(this.evcInternalSettle, (tokens, clearingPrices, trades, interactions)) | ||
| }); | ||
|
|
||
| // immediately after processing the swap, we should be skimming the result to the user | ||
| // we have to identify the trade associated with the vault to skim | ||
| /*ResolvedValues memory resolved; | ||
| for (uint256 i = 0; i < trades.length; i++) { | ||
| // the trade we are looking for is one where the receiver is the token itself | ||
| // if there are more than one trades that have this pattern, then something wierd is happening and we need to exit | ||
| console.log("check trades", i); | ||
| console.log("recvr", trades[i].receiver); | ||
| console.log("tokens", address(tokens[1])); | ||
| if (trades[i].receiver == address(tokens[trades[i].buyTokenIndex])) { | ||
| // we have to derive from the trade | ||
| RecoveredOrder memory order; | ||
| recoverOrderFromTrade(order, tokens, trades[i]); | ||
|
|
||
| if (resolved.vault != address(0)) { | ||
| revert MultiplePossibleReceivers(resolved.vault, resolved.sender, trades[i].receiver, order.owner); | ||
| } | ||
| resolved.vault = trades[i].receiver; | ||
| resolved.sender = order.owner; | ||
| resolved.minAmount = trades[i].buyAmount; | ||
| } | ||
| } | ||
|
|
||
| if (resolved.vault == address(0)) { | ||
| revert NotEVCSettlement(); | ||
| } | ||
|
|
||
| console.log("resolved vault ", resolved.vault); | ||
| console.log("resolved sender", resolved.sender); | ||
| console.log("resolved minAmount", resolved.minAmount); | ||
|
|
||
| items[preSettlementItems.length + 1] = IEVC.BatchItem({ | ||
| onBehalfOfAccount: address(this), | ||
| targetContract: address(this), | ||
| value: 0, | ||
| data: abi.encodeCall( | ||
| SwapVerifier.verifyAmountMinAndSkim, (resolved.vault, resolved.sender, resolved.minAmount, block.timestamp) | ||
| ) | ||
| });*/ | ||
|
|
||
| // Add skim call to the | ||
|
|
||
| // Copy post-settlement items | ||
| for (uint256 i = 0; i < postSettlementItems.length; i++) { | ||
| // At least one of the post settlement items should be skimming back to the user | ||
| items[preSettlementItems.length + 1 + i] = postSettlementItems[i]; | ||
| } | ||
|
|
||
| // Execute all items in a single batch | ||
| EVC.batch(items); | ||
| settleState = 0; | ||
| } | ||
|
|
||
| /// @notice Executes a batch of EVC operations | ||
| /// @notice Internal settlement function called by EVC | ||
| /// @param tokens Tokens involved in settlement | ||
| /// @param clearingPrices Clearing prices for settlement | ||
| /// @param trades Trade data for settlement | ||
| /// @param interactions Interaction data for settlement | ||
| function internalSettle( | ||
| function evcInternalSettle( | ||
| IERC20[] calldata tokens, | ||
| uint256[] calldata clearingPrices, | ||
| GPv2Trade.Data[] calldata trades, | ||
|
|
@@ -158,142 +111,15 @@ contract CowEvcWrapper is GPv2Signing, SwapVerifier { | |
| revert Unauthorized(msg.sender); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The assumption here is that the EVC is fully trusted to forward this call without changing the parameters. Is this true? |
||
| } | ||
|
|
||
| // Revert if not a valid solver | ||
| if (!IGPv2Authentication(SETTLEMENT.authenticator()).isSolver(origSender)) { | ||
| revert("CowEvcWrapper: not a solver"); | ||
| if (settleState != 1) { | ||
| // origSender will be address(0) here which indiates that internal settle was called when it shouldn't be (outside of wrappedSettle call) | ||
| revert Unauthorized(address(0)); | ||
| } | ||
|
|
||
| SETTLEMENT.settle(tokens, clearingPrices, trades, interactions); | ||
| } | ||
|
|
||
| function _copyToTransientStorage(IEVC.BatchItem[] memory batch, bytes32 startSlot) internal { | ||
| assembly { | ||
| // Get the memory pointer and length of the input array. | ||
| let batchPtr := batch | ||
| let batchLen := mload(batchPtr) | ||
|
|
||
| // A counter for the transient storage slot key. | ||
| let slotCounter := startSlot | ||
|
|
||
| // Store the overall array length at the start slot. | ||
| tstore(slotCounter, batchLen) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| // Iterate over each struct in the array. | ||
| for { let i := 0 } lt(i, batchLen) { i := add(i, 1) } { | ||
| // Each struct in a memory array occupies a fixed size plus the | ||
| // dynamic data's pointer. In this case, 4 words: 3 for fixed fields, | ||
| // and 1 for the pointer to the `bytes` data. | ||
| let structPtr := mload(add(batchPtr, mul(0x20, add(1, i)))) | ||
|
|
||
| // Load and store the fixed-size fields. | ||
| // Field 1: `targetContract` | ||
| let target := mload(structPtr) | ||
| tstore(slotCounter, target) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| // Field 2: `onBehalfOf` | ||
| let onBehalf := mload(add(structPtr, 0x20)) | ||
| tstore(slotCounter, onBehalf) | ||
| slotCounter := add(slotCounter, 1) | ||
| settleState = 2; | ||
|
|
||
| // Field 3: `value` | ||
| let val := mload(add(structPtr, 0x40)) | ||
| tstore(slotCounter, val) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| // Handle the dynamic-length `bytes` field. | ||
| // This is the most complex part of the process. | ||
| // The memory word for `data` is a pointer to the actual data. | ||
| let dataPtr := mload(add(structPtr, 0x60)) | ||
| let dataLen := mload(dataPtr) | ||
|
|
||
| // Store the length of the `bytes` data. | ||
| tstore(slotCounter, dataLen) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| // Calculate the number of 32-byte words for the `bytes` data. | ||
| let dataWords := div(add(dataLen, 31), 32) | ||
| let currentDataPtr := add(dataPtr, 0x20) // Skip the length word | ||
|
|
||
| // Loop to copy each word of the bytes data. | ||
| for { let j := 0 } lt(j, dataWords) { j := add(j, 1) } { | ||
| let word := mload(add(currentDataPtr, mul(j, 0x20))) | ||
| tstore(slotCounter, word) | ||
| slotCounter := add(slotCounter, 1) | ||
| } | ||
| } | ||
| } | ||
| // Use GPv2Wrapper's _internalSettle to call the settlement contract | ||
| _internalSettle(tokens, clearingPrices, trades, interactions); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Reads an array of `BatchType` structs from transient storage. | ||
| * @dev This function is for testing purposes to verify that the `copy` function | ||
| * works correctly. It reads from the same slot layout used for writing. | ||
| * @param startSlot The starting transient storage slot. | ||
| * @return retVal The array of structs read from transient storage. | ||
| */ | ||
| function _readFromTransientStorage(bytes32 startSlot) internal view returns (IEVC.BatchItem[] memory retVal) { | ||
| assembly { | ||
| // Get the overall array length from the start slot. | ||
| let slotCounter := startSlot | ||
| let batchLen := tload(slotCounter) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| // Allocate memory for the return array. | ||
| // A struct with 4 fields occupies 4 * 32 bytes = 128 bytes in memory. | ||
| let batchPtr := mload(0x40) | ||
| let endOfArray := add(batchPtr, add(0x20, mul(batchLen, 0x20))) | ||
| mstore(0x40, endOfArray) | ||
| mstore(batchPtr, batchLen) | ||
|
|
||
| // Iterate through the transient storage slots to reconstruct each struct. | ||
| for { let i := 0 } lt(i, batchLen) { i := add(i, 1) } { | ||
| let structPtr := mload(0x40) | ||
| let endOfData := add(structPtr, 0x80) | ||
| mstore(0x40, endOfData) | ||
|
|
||
| // Load and store the fixed-size fields. | ||
| mstore(add(structPtr, 0x00), tload(slotCounter)) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| mstore(add(structPtr, 0x20), tload(slotCounter)) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| mstore(add(structPtr, 0x40), tload(slotCounter)) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| // Load the length of the dynamic `bytes` field. | ||
| let dataLen := tload(slotCounter) | ||
| // Calculate the number of words to copy. | ||
| let dataWords := div(add(dataLen, 31), 32) | ||
| slotCounter := add(slotCounter, 1) | ||
|
|
||
| // Allocate memory for the `bytes` data. | ||
| let dataPtr := mload(0x40) | ||
| endOfData := add(dataPtr, add(0x20, mul(0x20, dataWords))) | ||
| mstore(0x40, endOfData) | ||
| mstore(dataPtr, dataLen) | ||
|
|
||
| let currentDataPtr := add(dataPtr, 0x20) | ||
|
|
||
| // Loop to load and copy each word of the bytes data. | ||
| for { let j := 0 } lt(j, dataWords) { j := add(j, 1) } { | ||
| mstore(add(currentDataPtr, mul(j, 0x20)), tload(slotCounter)) | ||
| slotCounter := add(slotCounter, 1) | ||
| } | ||
|
|
||
| // Store the pointer to the `bytes` data in the main struct. | ||
| mstore(add(structPtr, 0x60), dataPtr) | ||
|
|
||
| // Store the pointer to the structure in the array | ||
| mstore(add(batchPtr, mul(0x20, add(1, i))), structPtr) | ||
| } | ||
|
|
||
| // Return the memory pointer to the reconstructed array. | ||
| mstore(0x40, msize()) | ||
| retVal := batchPtr | ||
| //return(batchPtr, sub(endOfArray, batchPtr)) | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this binary blob about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it comes from my editor :/ it gets automatically added to all my projects. but yes it shouldnt be here