Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

env:
FOUNDRY_PROFILE: ci
FORK_RPC_URL: ${{ secrets.RPC_URL_1 }}

jobs:
check:
Expand All @@ -31,7 +32,7 @@ jobs:

- name: Run Forge build
run: |
forge build --sizes
forge build
id: build

- name: Run Forge tests
Expand Down
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[submodule "lib/cow"]
path = lib/cow
url = https://github.com/cowprotocol/contracts
branch = feat/wrapper
[submodule "lib/euler-vault-kit"]
path = lib/euler-vault-kit
url = https://github.com/euler-xyz/euler-vault-kit
Expand Down
13 changes: 8 additions & 5 deletions foundry.lock
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
{
"lib/cow": {
"rev": "39d7f4d68e37d14adeaf3c0caca30ea5c1a2fad9"
"branch": {
"name": "feat/wrapper",
"rev": "1e8127f476f8fef7758cf25033a0010d325dba8d"
}
},
"lib/euler-vault-kit": {
"rev": "5b98b42048ba11ae82fb62dfec06d1010c8e41e6"
},
"lib/evc": {
"rev": "34bb788288a0eb0fbba06bc370cb8ca3dd42614e"
},
"lib/forge-std": {
"tag": {
"name": "v1.10.0",
"rev": "8bbcf6e3f8f62f419e5429a0bd89331c85c37824"
}
"rev": "0768d9c08c085c79bb31d88683a78770764fec49"
}
}
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
src = "src"
out = "out"
libs = ["lib"]
optimize = true

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
111 changes: 111 additions & 0 deletions soljson-latest.js

Large diffs are not rendered by default.

217 changes: 42 additions & 175 deletions src/CowEvcWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,75 @@
pragma solidity ^0.8;

import {IEVC} from "evc/EthereumVaultConnector.sol";
import {IGPv2Settlement, GPv2Interaction, GPv2Trade} from "./vendor/interfaces/IGPv2Settlement.sol";
//import {IGPv2Settlement, GPv2Interaction} from "./vendor/interfaces/IGPv2Settlement.sol";
import {IGPv2Authentication} from "./vendor/interfaces/IGPv2Authentication.sol";

import "forge-std/console.sol";
import {GPv2Signing, IERC20, GPv2Trade} from "cow/mixins/GPv2Signing.sol";
import {GPv2Wrapper,GPv2Interaction} from "cow/GPv2Wrapper.sol";

import {SwapVerifier} from "./SwapVerifier.sol";

/// @title CowEvcWrapper
/// @notice A wrapper around the EVC that allows for settlement operations
contract CowEvcWrapper {
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();
error MultiplePossibleReceivers(
address resolvedVault, address resolvedSender, address secondVault, address secondSender
);

error NotEVCSettlement();

constructor(address _evc, address payable _settlement) {
constructor(address _evc, address payable _settlement) GPv2Wrapper(_settlement) {
EVC = IEVC(_evc);
SETTLEMENT = IGPv2Settlement(_settlement);
}

/// @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"));
struct ResolvedValues {
address vault;
address sender;
uint256 minAmount;
}

/// @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(
address[] calldata tokens,
/// @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 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))
});

// Copy post-settlement items
Expand All @@ -85,15 +80,16 @@ contract CowEvcWrapper {

// 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(
address[] calldata tokens,
function evcInternalSettle(
IERC20[] calldata tokens,
uint256[] calldata clearingPrices,
GPv2Trade.Data[] calldata trades,
GPv2Interaction.Data[][3] calldata interactions
Expand All @@ -102,143 +98,14 @@ contract CowEvcWrapper {
revert Unauthorized(msg.sender);
}

// 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));
}

settleState = 2;

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)

// 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)
}
}
}
}

/**
* @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))
}
// Use GPv2Wrapper's _internalSettle to call the settlement contract
_internalSettle(tokens, clearingPrices, trades, interactions);
}
}
1 change: 0 additions & 1 deletion src/SwapVerifier.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.8;

import {IEVault, IERC20} from "euler-vault-kit/src/EVault/IEVault.sol";
Expand Down
30 changes: 0 additions & 30 deletions test/CowEvcWrapperTest.internal.t.sol

This file was deleted.

Loading
Loading