Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jj1980a committed Nov 3, 2023
1 parent 46fc76d commit 7ca02fc
Showing 1 changed file with 87 additions and 83 deletions.
170 changes: 87 additions & 83 deletions src/contracts/examples/intents-example/V4SwapIntent.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@ import {DAppControl} from "../../dapp/DAppControl.sol";

import "forge-std/Test.sol";

// This is the SwapIntent that the user inputs
struct SwapIntent {
address tokenUserBuys;
uint256 amountUserBuys;
address tokenUserSells;
uint256 amountUserSells;
address auctionBaseCurrency; // NOTE: Typically will be address(0) / ETH for gas refund
bool solverMustReimburseGas; // If true, the solver must reimburse the bundler for the user's and control's gas cost
// Those are the intents that the user inputs
struct ExactInputIntent {
address tokenIn;
address tokenOut;
uint256 maxFee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint256 sqrtPriceLimitX96;
}

struct ExactOutputIntent {
address tokenIn;
address tokenOut;
uint256 maxFee;
address recipient;
uint256 amountInMaximum;
uint256 amountOut;
uint256 sqrtPriceLimitX96;
}

// This struct is for passing around data internally
Expand All @@ -37,19 +48,18 @@ struct SwapData {
uint256 solverGasLiability; // the amount of user gas that the solver must refund
}


contract V4SwapIntentController is DAppControl {
using SafeTransferLib for ERC20;

uint256 constant public EXPECTED_GAS_USAGE_EX_SOLVER = 200_000;
uint256 public constant EXPECTED_GAS_USAGE_EX_SOLVER = 200_000;
address constant V4_POOL = address(0);

uint256 startingBalance; // Balance tracked for the v4 pool

constructor(address _escrow)
DAppControl(
_escrow,
msg.sender,
_escrow,
msg.sender,
CallConfig({
sequenced: false,
requirePreOps: false,
Expand Down Expand Up @@ -84,7 +94,7 @@ contract V4SwapIntentController is DAppControl {

// TODO: Could maintain a balance of "1" of each token to allow the user to save gas over multiple uses
uint256 tokenInBalance = ERC20(tokenIn).balanceOf(address(this));
if (tokenInBalance > 0) {
if (tokenInBalance > 0) {
ERC20(tokenIn).safeTransfer(user, tokenInBalance);
}

Expand All @@ -100,44 +110,36 @@ contract V4SwapIntentController is DAppControl {
_;
}

// selector 0x04e45aaf
function exactInputSingle(
address tokenIn,
address tokenOut,
uint256 maxFee,
address recipient,
uint256 amountIn,
uint256 amountOutMinimum,
uint256 sqrtPriceLimitX96
) external payable verifyCall(tokenIn, tokenOut, amountIn) returns (SwapData memory) {
function exactInputSingle(ExactInputIntent calldata exactInputIntent)
external
payable
verifyCall(exactInputIntent.tokenIn, exactInputIntent.tokenOut, exactInputIntent.amountIn)
returns (SwapData memory)
{
SwapData memory swapData = SwapData({
tokenIn: tokenIn,
tokenOut: tokenOut,
requestedAmount: int256(amountIn),
limitAmount: amountOutMinimum,
recipient: recipient,
tokenIn: exactInputIntent.tokenIn,
tokenOut: exactInputIntent.tokenOut,
requestedAmount: int256(exactInputIntent.amountIn),
limitAmount: exactInputIntent.amountOutMinimum,
recipient: exactInputIntent.recipient,
solverGasLiability: EXPECTED_GAS_USAGE_EX_SOLVER
});

return swapData;
}

// selector 0x5023b4df
function exactOutputSingle(
address tokenIn,
address tokenOut,
uint256 maxFee,
address recipient,
uint256 amountInMaximum,
uint256 amountOut,
uint256 sqrtPriceLimitX96
) external payable verifyCall(tokenIn, tokenOut, amountInMaximum) returns (SwapData memory) {
}

function exactOutputSingle(ExactOutputIntent calldata exactOutputIntent)
external
payable
verifyCall(exactOutputIntent.tokenIn, exactOutputIntent.tokenOut, exactOutputIntent.amountInMaximum)
returns (SwapData memory)
{
SwapData memory swapData = SwapData({
tokenIn: tokenIn,
tokenOut: tokenOut,
requestedAmount: -int256(amountOut),
limitAmount: amountInMaximum,
recipient: recipient,
tokenIn: exactOutputIntent.tokenIn,
tokenOut: exactOutputIntent.tokenOut,
requestedAmount: -int256(exactOutputIntent.amountOut),
limitAmount: exactOutputIntent.amountInMaximum,
recipient: exactOutputIntent.recipient,
solverGasLiability: EXPECTED_GAS_USAGE_EX_SOLVER
});

Expand All @@ -157,25 +159,26 @@ contract V4SwapIntentController is DAppControl {
SwapData memory swapData = abi.decode(returnData, (SwapData));

// Record balance and transfer to the solver
if(swapData.requestedAmount > 0) { // exact input
if (swapData.requestedAmount > 0) {
// exact input
startingBalance = ERC20(swapData.tokenIn).balanceOf(V4_POOL);
_transferUserERC20(swapData.tokenIn, solverTo, uint256(swapData.requestedAmount));
} else { // exact output
} else {
// exact output
startingBalance = ERC20(swapData.tokenOut).balanceOf(V4_POOL);
_transferUserERC20(swapData.tokenIn, solverTo, swapData.limitAmount - solverBid);
// For exact output swaps, the solver solvers compete and bid on how much tokens they can
// For exact output swaps, the solvers compete and bid on how much tokens they can
// return to the user in excess of their specified limit input. We only transfer what they
// require to make the swap in this step.
}

// TODO: Permit69 is currently enabled during solver phase, but there is low conviction that this
// does not enable an attack vector. Consider enabling to save gas on a transfer?
return true;
}

// Checking intent was fulfilled, and user has received their tokens, happens here
function _postSolverCall(bytes calldata data) internal override returns (bool) {

(address solverTo, uint256 solverBid, bytes memory returnData) = abi.decode(data, (address, uint256, bytes));

SwapData memory swapData = abi.decode(returnData, (SwapData));
Expand All @@ -195,37 +198,37 @@ contract V4SwapIntentController is DAppControl {
}

uint256 buyTokenBalance = ERC20(swapData.tokenOut).balanceOf(address(this));
uint256 amountUserBuys = swapData.requestedAmount > 0 ? swapData.limitAmount : uint256(-swapData.requestedAmount);

// If it was an exact input swap, we need to verify that
uint256 amountUserBuys =
swapData.requestedAmount > 0 ? swapData.limitAmount : uint256(-swapData.requestedAmount);

// If it was an exact input swap, we need to verify that
// a) We have enough tokens to meet the user's minimum amount out
// b) THe output amount matches (or is greater than) the solver's bid
if(swapData.requestedAmount > 0) {
if(buyTokenBalance < swapData.limitAmount) {
// b) The output amount matches (or is greater than) the solver's bid
if (swapData.requestedAmount > 0) {
if (buyTokenBalance < swapData.limitAmount) {
return false; // insufficient amount out
}
if(buyTokenBalance < solverBid) {
if (buyTokenBalance < solverBid) {
return false; // does not meet solver bid
}
}

// no need to check for exact output, since the max is whatever the user transferred

if (buyTokenBalance >= amountUserBuys) {
if (buyTokenBalance < amountUserBuys) {
return false;
}

// Make sure not to transfer any extra 'auctionBaseCurrency' token, since that will be used
// for the auction measurements
address auctionBaseCurrency = swapData.requestedAmount > 0 ? swapData.tokenOut : swapData.tokenIn;
// Make sure not to transfer any extra 'auctionBaseCurrency' token, since that will be used
// for the auction measurements
address auctionBaseCurrency = swapData.requestedAmount > 0 ? swapData.tokenOut : swapData.tokenIn;

if (swapData.tokenOut != auctionBaseCurrency) {
ERC20(swapData.tokenOut).safeTransfer(swapData.recipient, buyTokenBalance);
} else {
ERC20(swapData.tokenOut).safeTransfer(swapData.recipient, amountUserBuys);
}
return true;

if (swapData.tokenOut != auctionBaseCurrency) {
ERC20(swapData.tokenOut).safeTransfer(swapData.recipient, buyTokenBalance);
} else {
return false;
ERC20(swapData.tokenOut).safeTransfer(swapData.recipient, amountUserBuys);
}
return true;
}

// This occurs after a Solver has successfully paid their bid, which is
Expand All @@ -236,19 +239,19 @@ contract V4SwapIntentController is DAppControl {
// msg.sender = Escrow

// NOTE: donateToBundler caps the donation at 110% of total gas cost.
// Any remainder is then sent to the specified recipient.
// Any remainder is then sent to the specified recipient.
// IEscrow(escrow).donateToBundler{value: address(this).balance}();
SwapData memory swapData = abi.decode(data, (SwapData));

if (bidToken != address(0)) {
ERC20(bidToken).safeTransfer(_user(), bidAmount);
// If the solver was already required to reimburse the user's gas, don't reallocate
// Ether surplus to the bundler

// If the solver was already required to reimburse the user's gas, don't reallocate
// Ether surplus to the bundler
} else if (swapData.solverGasLiability > 0) {
SafeTransferLib.safeTransferETH(_user(), address(this).balance);

// Donate the ether to the bundler, with the surplus going back to the user
// Donate the ether to the bundler, with the surplus going back to the user
} else {
console.log("donating to bundler in SwapIntent");
IEscrow(escrow).donateToBundler{value: address(this).balance}(_user());
Expand All @@ -265,17 +268,18 @@ contract V4SwapIntentController is DAppControl {
// so that they can get the proper format for
// submitting their bids to the hook.

(SwapIntent memory swapIntent) = abi.decode(userOp.data[4:], (SwapIntent));

bidToken = swapIntent.auctionBaseCurrency;
if (userOp.data[4:] == this.exactInputSingle.selector) {
ExactInputIntent memory exactInputIntent = abi.decode(userOp.data[4:], (ExactInputIntent));
bidToken = exactInputIntent.tokenOut;
} else if (userOp.data[4:] == this.exactOutputSingle.selector) {
ExactOutputIntent memory exactOutputIntent = abi.decode(userOp.data[4:], (ExactOutputIntent));
bidToken = exactOutputIntent.tokenIn;
} else {
revert("InvalidFunctionSelector");
}
}

function getBidValue(SolverOperation calldata solverOp)
public
pure
override
returns (uint256)
{
function getBidValue(SolverOperation calldata solverOp) public pure override returns (uint256) {
return solverOp.bidAmount;
}
}

0 comments on commit 7ca02fc

Please sign in to comment.