diff --git a/contracts/AMMWrapper.sol b/contracts/AMMWrapper.sol index 343983bb..552d3d3d 100644 --- a/contracts/AMMWrapper.sol +++ b/contracts/AMMWrapper.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.7.6; +pragma abicoder v2; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; @@ -114,30 +115,12 @@ contract AMMWrapper is IAMMWrapper, StrategyBase, ReentrancyGuard, BaseLibEIP712 * External functions * *************************************************************/ function trade( - address _makerAddr, - address _takerAssetAddr, - address _makerAssetAddr, - uint256 _takerAssetAmount, - uint256 _makerAssetAmount, + AMMLibEIP712.Order calldata _order, uint256 _feeFactor, - address _userAddr, - address payable _receiverAddr, - uint256 _salt, - uint256 _deadline, - bytes calldata _sig + bytes calldata _sig, + bytes calldata _takerAssetPermitSig ) external payable override nonReentrant onlyUserProxy returns (uint256) { - AMMLibEIP712.Order memory order = AMMLibEIP712.Order( - _makerAddr, - _takerAssetAddr, - _makerAssetAddr, - _takerAssetAmount, - _makerAssetAmount, - _userAddr, - _receiverAddr, - _salt, - _deadline - ); - require(order.deadline >= block.timestamp, "AMMWrapper: expired order"); + require(_order.deadline >= block.timestamp, "AMMWrapper: expired order"); // These variables are copied straight from function parameters and // used to bypass stack too deep error. @@ -151,36 +134,36 @@ contract AMMWrapper is IAMMWrapper, StrategyBase, ReentrancyGuard, BaseLibEIP712 } // Assign trade vairables - internalTxData.fromEth = (order.takerAssetAddr == LibConstant.ZERO_ADDRESS || order.takerAssetAddr == LibConstant.ETH_ADDRESS); - internalTxData.toEth = (order.makerAssetAddr == LibConstant.ZERO_ADDRESS || order.makerAssetAddr == LibConstant.ETH_ADDRESS); - if (_isCurve(order.makerAddr)) { + internalTxData.fromEth = (_order.takerAssetAddr == LibConstant.ZERO_ADDRESS || _order.takerAssetAddr == LibConstant.ETH_ADDRESS); + internalTxData.toEth = (_order.makerAssetAddr == LibConstant.ZERO_ADDRESS || _order.makerAssetAddr == LibConstant.ETH_ADDRESS); + if (_isCurve(_order.makerAddr)) { // PermanetStorage can recognize `ETH_ADDRESS` but not `ZERO_ADDRESS`. // Convert it to `ETH_ADDRESS` as passed in `order.takerAssetAddr` or `order.makerAssetAddr` might be `ZERO_ADDRESS`. - internalTxData.takerAssetInternalAddr = internalTxData.fromEth ? LibConstant.ETH_ADDRESS : order.takerAssetAddr; - internalTxData.makerAssetInternalAddr = internalTxData.toEth ? LibConstant.ETH_ADDRESS : order.makerAssetAddr; + internalTxData.takerAssetInternalAddr = internalTxData.fromEth ? LibConstant.ETH_ADDRESS : _order.takerAssetAddr; + internalTxData.makerAssetInternalAddr = internalTxData.toEth ? LibConstant.ETH_ADDRESS : _order.makerAssetAddr; } else { - internalTxData.takerAssetInternalAddr = internalTxData.fromEth ? address(weth) : order.takerAssetAddr; - internalTxData.makerAssetInternalAddr = internalTxData.toEth ? address(weth) : order.makerAssetAddr; + internalTxData.takerAssetInternalAddr = internalTxData.fromEth ? address(weth) : _order.takerAssetAddr; + internalTxData.makerAssetInternalAddr = internalTxData.toEth ? address(weth) : _order.makerAssetAddr; } - txMetaData.transactionHash = _verify(order, _sig); + txMetaData.transactionHash = _verify(_order, _sig); - _prepare(order, internalTxData); + _transferTakerAssetToAMM(_order, internalTxData, _takerAssetPermitSig); { // Set min amount for swap = _order.makerAssetAmount * (10000 / (10000 - feeFactor)) - uint256 swapMinOutAmount = order.makerAssetAmount.mul(LibConstant.BPS_MAX).div(LibConstant.BPS_MAX.sub(txMetaData.feeFactor)); - (txMetaData.source, txMetaData.receivedAmount) = _swap(order, internalTxData, swapMinOutAmount); + uint256 swapMinOutAmount = _order.makerAssetAmount.mul(LibConstant.BPS_MAX).div(LibConstant.BPS_MAX.sub(txMetaData.feeFactor)); + (txMetaData.source, txMetaData.receivedAmount) = _swap(_order, internalTxData, swapMinOutAmount); // Settle // Calculate fee using actually received from swap uint256 actualFee = txMetaData.receivedAmount.mul(txMetaData.feeFactor).div(LibConstant.BPS_MAX); txMetaData.settleAmount = txMetaData.receivedAmount.sub(actualFee); - require(txMetaData.settleAmount >= order.makerAssetAmount, "AMMWrapper: insufficient maker output"); - _settle(order, internalTxData, txMetaData.settleAmount, actualFee); + require(txMetaData.settleAmount >= _order.makerAssetAmount, "AMMWrapper: insufficient maker output"); + _settle(_order, internalTxData, txMetaData.settleAmount, actualFee); } - emitSwappedEvent(order, txMetaData); + emitSwappedEvent(_order, txMetaData); return txMetaData.settleAmount; } @@ -229,9 +212,13 @@ contract AMMWrapper is IAMMWrapper, StrategyBase, ReentrancyGuard, BaseLibEIP712 /** * @dev internal function of `trade`. - * It executes the swap on chosen AMM. + * It transfers assets from the taker to this contract with taker's permission. */ - function _prepare(AMMLibEIP712.Order memory _order, InternalTxData memory _internalTxData) internal { + function _transferTakerAssetToAMM( + AMMLibEIP712.Order memory _order, + InternalTxData memory _internalTxData, + bytes memory _takerAssetPermitSig + ) internal { // Transfer asset from user and deposit to weth if needed if (_internalTxData.fromEth) { require(msg.value > 0, "AMMWrapper: msg.value is zero"); @@ -241,8 +228,17 @@ contract AMMWrapper is IAMMWrapper, StrategyBase, ReentrancyGuard, BaseLibEIP712 weth.deposit{ value: msg.value }(); } } else { - // other ERC20 tokens - spender.spendFromUser(_order.userAddr, _order.takerAssetAddr, _order.takerAssetAmount); + // Transfer other ERC20 tokens to this contract + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = SpenderLibEIP712.SpendWithPermit({ + tokenAddr: _order.takerAssetAddr, + requester: address(this), + user: _order.userAddr, + recipient: address(this), + amount: _order.takerAssetAmount, + actionHash: AMMLibEIP712._getOrderHash(_order), + expiry: uint64(_order.deadline) + }); + spender.spendFromUserToWithPermit(takerAssetPermit, _takerAssetPermitSig); } } diff --git a/contracts/AMMWrapperWithPath.sol b/contracts/AMMWrapperWithPath.sol index dd55bd03..3918ffaa 100644 --- a/contracts/AMMWrapperWithPath.sol +++ b/contracts/AMMWrapperWithPath.sol @@ -51,59 +51,53 @@ contract AMMWrapperWithPath is IAMMWrapperWithPath, AMMWrapper { * External functions * *************************************************************/ - function trade( - AMMLibEIP712.Order calldata _order, - uint256 _feeFactor, - bytes calldata _sig, - bytes calldata _makerSpecificData, - address[] calldata _path - ) external payable override nonReentrant onlyUserProxy returns (uint256) { - require(_order.deadline >= block.timestamp, "AMMWrapper: expired order"); + function trade(IAMMWrapperWithPath.TradeWithPathParams calldata _params) external payable override nonReentrant onlyUserProxy returns (uint256) { + require(_params.order.deadline >= block.timestamp, "AMMWrapper: expired order"); // These variables are copied straight from function parameters and // used to bypass stack too deep error. TxMetaData memory txMetaData; InternalTxData memory internalTxData; - txMetaData.feeFactor = uint16(_feeFactor); + txMetaData.feeFactor = uint16(_params.feeFactor); txMetaData.relayed = permStorage.isRelayerValid(tx.origin); - internalTxData.makerSpecificData = _makerSpecificData; - internalTxData.path = _path; + internalTxData.makerSpecificData = _params.makerSpecificData; + internalTxData.path = _params.path; if (!txMetaData.relayed) { // overwrite feeFactor with defaultFeeFactor if not from valid relayer txMetaData.feeFactor = defaultFeeFactor; } // Assign trade vairables - internalTxData.fromEth = (_order.takerAssetAddr == LibConstant.ZERO_ADDRESS || _order.takerAssetAddr == LibConstant.ETH_ADDRESS); - internalTxData.toEth = (_order.makerAssetAddr == LibConstant.ZERO_ADDRESS || _order.makerAssetAddr == LibConstant.ETH_ADDRESS); - if (_isCurve(_order.makerAddr)) { + internalTxData.fromEth = (_params.order.takerAssetAddr == LibConstant.ZERO_ADDRESS || _params.order.takerAssetAddr == LibConstant.ETH_ADDRESS); + internalTxData.toEth = (_params.order.makerAssetAddr == LibConstant.ZERO_ADDRESS || _params.order.makerAssetAddr == LibConstant.ETH_ADDRESS); + if (_isCurve(_params.order.makerAddr)) { // PermanetStorage can recognize `ETH_ADDRESS` but not `ZERO_ADDRESS`. // Convert it to `ETH_ADDRESS` as passed in `_order.takerAssetAddr` or `_order.makerAssetAddr` might be `ZERO_ADDRESS`. - internalTxData.takerAssetInternalAddr = internalTxData.fromEth ? LibConstant.ETH_ADDRESS : _order.takerAssetAddr; - internalTxData.makerAssetInternalAddr = internalTxData.toEth ? LibConstant.ETH_ADDRESS : _order.makerAssetAddr; + internalTxData.takerAssetInternalAddr = internalTxData.fromEth ? LibConstant.ETH_ADDRESS : _params.order.takerAssetAddr; + internalTxData.makerAssetInternalAddr = internalTxData.toEth ? LibConstant.ETH_ADDRESS : _params.order.makerAssetAddr; } else { - internalTxData.takerAssetInternalAddr = internalTxData.fromEth ? address(weth) : _order.takerAssetAddr; - internalTxData.makerAssetInternalAddr = internalTxData.toEth ? address(weth) : _order.makerAssetAddr; + internalTxData.takerAssetInternalAddr = internalTxData.fromEth ? address(weth) : _params.order.takerAssetAddr; + internalTxData.makerAssetInternalAddr = internalTxData.toEth ? address(weth) : _params.order.makerAssetAddr; } - txMetaData.transactionHash = _verify(_order, _sig); + txMetaData.transactionHash = _verify(_params.order, _params.sig); - _prepare(_order, internalTxData); + _transferTakerAssetToAMM(_params.order, internalTxData, _params.takerAssetPermitSig); { // Set min amount for swap = _order.makerAssetAmount * (10000 / (10000 - feeFactor)) - uint256 swapMinOutAmount = _order.makerAssetAmount.mul(LibConstant.BPS_MAX).div(LibConstant.BPS_MAX.sub(txMetaData.feeFactor)); - (txMetaData.source, txMetaData.receivedAmount) = _swapWithPath(_order, internalTxData, swapMinOutAmount); + uint256 swapMinOutAmount = _params.order.makerAssetAmount.mul(LibConstant.BPS_MAX).div(LibConstant.BPS_MAX.sub(txMetaData.feeFactor)); + (txMetaData.source, txMetaData.receivedAmount) = _swapWithPath(_params.order, internalTxData, swapMinOutAmount); // Settle // Calculate fee using actually received from swap uint256 actualFee = txMetaData.receivedAmount.mul(txMetaData.feeFactor).div(LibConstant.BPS_MAX); txMetaData.settleAmount = txMetaData.receivedAmount.sub(actualFee); - require(txMetaData.settleAmount >= _order.makerAssetAmount, "AMMWrapper: insufficient maker output"); - _settle(_order, internalTxData, txMetaData.settleAmount, actualFee); + require(txMetaData.settleAmount >= _params.order.makerAssetAmount, "AMMWrapper: insufficient maker output"); + _settle(_params.order, internalTxData, txMetaData.settleAmount, actualFee); } - emitSwappedEvent(_order, txMetaData); + emitSwappedEvent(_params.order, txMetaData); return txMetaData.settleAmount; } diff --git a/contracts/L2Deposit.sol b/contracts/L2Deposit.sol index 7d5e4329..af605017 100644 --- a/contracts/L2Deposit.sol +++ b/contracts/L2Deposit.sol @@ -11,6 +11,7 @@ import "./interfaces/IL2Deposit.sol"; import "./interfaces/IOptimismL1StandardBridge.sol"; import "./interfaces/IPermanentStorage.sol"; import "./interfaces/ISpender.sol"; +import "./utils/SpenderLibEIP712.sol"; import "./utils/StrategyBase.sol"; import "./utils/BaseLibEIP712.sol"; import "./utils/Ownable.sol"; @@ -49,8 +50,19 @@ contract L2Deposit is IL2Deposit, StrategyBase, ReentrancyGuard, BaseLibEIP712, // PermanentStorage will revert when L2 deposit hash is already seen permStorage.setL2DepositSeen(depositHash); - // Transfer token from sender to this contract - spender.spendFromUser(_params.deposit.sender, _params.deposit.l1TokenAddr, _params.deposit.amount); + // Create spendWithPermit for spender contract to verify + SpenderLibEIP712.SpendWithPermit memory l1TokenPermit = SpenderLibEIP712.SpendWithPermit({ + tokenAddr: _params.deposit.l1TokenAddr, + requester: address(this), + user: _params.deposit.sender, + recipient: address(this), + amount: _params.deposit.amount, + actionHash: depositHash, + expiry: uint64(_params.deposit.expiry) + }); + + // Verfiy signature then transfer token from sender to this contract + spender.spendFromUserToWithPermit(l1TokenPermit, _params.l1TokenPermitSig); // Bypass stack too deep DepositInfo memory depositInfo = DepositInfo( diff --git a/contracts/RFQ.sol b/contracts/RFQ.sol index f694909e..ea700236 100644 --- a/contracts/RFQ.sol +++ b/contracts/RFQ.sol @@ -2,6 +2,8 @@ pragma solidity 0.7.6; pragma abicoder v2; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/Address.sol"; @@ -10,6 +12,7 @@ import "./interfaces/IRFQ.sol"; import "./utils/StrategyBase.sol"; import "./utils/RFQLibEIP712.sol"; import "./utils/BaseLibEIP712.sol"; +import "./utils/SpenderLibEIP712.sol"; import "./utils/SignatureValidator.sol"; import "./utils/LibConstant.sol"; @@ -18,6 +21,7 @@ import "./utils/LibConstant.sol"; contract RFQ is IRFQ, StrategyBase, ReentrancyGuard, SignatureValidator, BaseLibEIP712 { using SafeMath for uint256; using Address for address; + using SafeERC20 for IERC20; // Constants do not have storage slot. string public constant SOURCE = "RFQ v1"; @@ -98,7 +102,9 @@ contract RFQ is IRFQ, StrategyBase, ReentrancyGuard, SignatureValidator, BaseLib function fill( RFQLibEIP712.Order calldata _order, bytes calldata _mmSignature, - bytes calldata _userSignature + bytes calldata _userSignature, + bytes calldata _makerAssetPermitSig, + bytes calldata _takerAssetPermitSig ) external payable override nonReentrant onlyUserProxy returns (uint256) { // check the order deadline and fee factor require(_order.deadline >= block.timestamp, "RFQ: expired order"); @@ -115,7 +121,7 @@ contract RFQ is IRFQ, StrategyBase, ReentrancyGuard, SignatureValidator, BaseLib // Set transaction as seen, PermanentStorage would throw error if transaction already seen. permStorage.setRFQTransactionSeen(vars.transactionHash); - return _settle(_order, vars); + return _settle(_order, vars, _makerAssetPermitSig, _takerAssetPermitSig); } function _emitFillOrder( @@ -140,7 +146,34 @@ contract RFQ is IRFQ, StrategyBase, ReentrancyGuard, SignatureValidator, BaseLib } // settle - function _settle(RFQLibEIP712.Order memory _order, GroupedVars memory _vars) internal returns (uint256) { + function _settle( + RFQLibEIP712.Order memory _order, + GroupedVars memory _vars, + bytes memory _makerAssetPermitSig, + bytes memory _takerAssetPermitSig + ) internal returns (uint256) { + // Declare the 'maker sends makerAsset to this contract' SpendWithPermit struct from _order parameter + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit = SpenderLibEIP712.SpendWithPermit( + _order.makerAssetAddr, + address(this), + _order.makerAddr, + address(this), + _order.makerAssetAmount, + _vars.orderHash, + uint64(_order.deadline) + ); + + // Declare the 'taker sends takerAsset to this contract' SpendWithPermit struct from _order parameter + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = SpenderLibEIP712.SpendWithPermit( + _order.takerAssetAddr, + address(this), + _order.takerAddr, + address(this), + _order.takerAssetAmount, + _vars.transactionHash, + uint64(_order.deadline) + ); + // Transfer taker asset to maker if (address(weth) == _order.takerAssetAddr) { // Deposit to WETH if taker asset is ETH @@ -148,7 +181,10 @@ contract RFQ is IRFQ, StrategyBase, ReentrancyGuard, SignatureValidator, BaseLib weth.deposit{ value: msg.value }(); weth.transfer(_order.makerAddr, _order.takerAssetAmount); } else { - spender.spendFromUserTo(_order.takerAddr, _order.takerAssetAddr, _order.makerAddr, _order.takerAssetAmount); + // Transfer taker asset to this contract first, + spender.spendFromUserToWithPermit(takerAssetPermit, _takerAssetPermitSig); + // then transfer from this to maker. + IERC20(_order.takerAssetAddr).safeTransfer(_order.makerAddr, _order.takerAssetAmount); } // Transfer maker asset to taker, sub fee @@ -158,18 +194,20 @@ contract RFQ is IRFQ, StrategyBase, ReentrancyGuard, SignatureValidator, BaseLib settleAmount = settleAmount.sub(fee); } - // Transfer token/Eth to receiver + // Transfer maker asset to this contract first + spender.spendFromUserToWithPermit(makerAssetPermit, _makerAssetPermitSig); + + // Transfer maker asset less fee from this contract to receiver if (_order.makerAssetAddr == address(weth)) { - // Transfer from maker - spender.spendFromUser(_order.makerAddr, _order.makerAssetAddr, settleAmount); weth.withdraw(settleAmount); payable(_order.receiverAddr).transfer(settleAmount); } else { - spender.spendFromUserTo(_order.makerAddr, _order.makerAssetAddr, _order.receiverAddr, settleAmount); + IERC20(_order.makerAssetAddr).safeTransfer(_order.receiverAddr, settleAmount); } - // Collect fee + + // Transfer fee from this contract to feeCollector if (fee > 0) { - spender.spendFromUserTo(_order.makerAddr, _order.makerAssetAddr, feeCollector, fee); + IERC20(_order.makerAssetAddr).safeTransfer(feeCollector, fee); } _emitFillOrder(_order, _vars, settleAmount); diff --git a/contracts/Spender.sol b/contracts/Spender.sol index e370c00c..9df57418 100644 --- a/contracts/Spender.sol +++ b/contracts/Spender.sol @@ -1,18 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity 0.7.6; +pragma abicoder v2; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./utils/LibConstant.sol"; import "./utils/Ownable.sol"; +import "./utils/SpenderLibEIP712.sol"; +import "./utils/BaseLibEIP712.sol"; +import "./utils/SignatureValidator.sol"; import "./interfaces/ISpender.sol"; import "./interfaces/IAllowanceTarget.sol"; /** * @dev Spender contract */ -contract Spender is ISpender, Ownable { +contract Spender is ISpender, Ownable, BaseLibEIP712, SignatureValidator { using SafeMath for uint256; // Constants do not have storage slot. @@ -32,6 +36,7 @@ contract Spender is ISpender, Ownable { mapping(address => bool) public consumeGasERC20Tokens; mapping(uint256 => address) public pendingAuthorized; mapping(address => bool) private authorized; + mapping(bytes32 => bool) private spendingFulfilled; // Store the fulfilled spending hash mapping(address => bool) private tokenBlacklist; /************************************************************ @@ -164,18 +169,6 @@ contract Spender is ISpender, Ownable { /************************************************************ * External functions * *************************************************************/ - /// @dev Spend tokens on user's behalf. Only an authority can call this. - /// @param _user The user to spend token from. - /// @param _tokenAddr The address of the token. - /// @param _amount Amount to spend. - function spendFromUser( - address _user, - address _tokenAddr, - uint256 _amount - ) external override onlyAuthorized { - _transferTokenFromUserTo(_user, _tokenAddr, msg.sender, _amount); - } - /// @dev Spend tokens on user's behalf. Only an authority can call this. /// @param _user The user to spend token from. /// @param _tokenAddr The address of the token. @@ -190,6 +183,27 @@ contract Spender is ISpender, Ownable { _transferTokenFromUserTo(_user, _tokenAddr, _recipient, _amount); } + /// @dev Spend tokens on user's behalf with user's permit signature. Only an authority can call this. + /// @param _params The params of the SpendWithPermit. + /// @param _spendWithPermitSig Spend with permit signature. + function spendFromUserToWithPermit(SpenderLibEIP712.SpendWithPermit calldata _params, bytes calldata _spendWithPermitSig) external override onlyAuthorized { + // Check expiry timestamp + require(_params.expiry >= block.timestamp, "Spender: Permit is expired"); + // Check requester validity + require(_params.requester == msg.sender, "Spender: invalid requester address"); + + // Validate spend with permit signature + bytes32 spendWithPermitHash = getEIP712Hash(SpenderLibEIP712._getSpendWithPermitHash(_params)); + + // Validate spending is not replayed + require(!spendingFulfilled[spendWithPermitHash], "Spender: Permit is already fulfilled"); + spendingFulfilled[spendWithPermitHash] = true; + + require(isValidSignature(_params.user, spendWithPermitHash, bytes(""), _spendWithPermitSig), "Spender: Invalid permit signature"); + + _transferTokenFromUserTo(_params.user, _params.tokenAddr, _params.recipient, _params.amount); + } + function _transferTokenFromUserTo( address _user, address _tokenAddr, diff --git a/contracts/SpenderSimulation.sol b/contracts/SpenderSimulation.sol index 3eaa7ba9..4df31085 100644 --- a/contracts/SpenderSimulation.sol +++ b/contracts/SpenderSimulation.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.7.6; +pragma abicoder v2; import "./interfaces/IHasBlackListERC20Token.sol"; import "./interfaces/ISpender.sol"; +import "./utils/SpenderLibEIP712.sol"; contract SpenderSimulation { ISpender public immutable spender; @@ -33,15 +35,13 @@ contract SpenderSimulation { *************************************************************/ /// @dev Spend tokens on user's behalf but reverts if succeed. /// This is only intended to be run off-chain to check if the transfer will succeed. - /// @param _user The user to spend token from. - /// @param _tokenAddr The address of the token. - /// @param _amount Amount to spend. - function simulate( - address _user, - address _tokenAddr, - uint256 _amount - ) external checkBlackList(_tokenAddr, _user) { - spender.spendFromUser(_user, _tokenAddr, _amount); + /// @param _params The params of the SpendWithPermit. + /// @param _spendWithPermitSig Spend with permit signature. + function simulate(SpenderLibEIP712.SpendWithPermit calldata _params, bytes calldata _spendWithPermitSig) + external + checkBlackList(_params.tokenAddr, _params.user) + { + spender.spendFromUserToWithPermit(_params, _spendWithPermitSig); // All checks passed: revert with success reason string revert("SpenderSimulation: transfer simulation success"); diff --git a/contracts/interfaces/IAMMWrapper.sol b/contracts/interfaces/IAMMWrapper.sol index 5cf9c339..ee3e6d39 100644 --- a/contracts/interfaces/IAMMWrapper.sol +++ b/contracts/interfaces/IAMMWrapper.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0; +pragma abicoder v2; import "./IStrategyBase.sol"; +import "../utils/AMMLibEIP712.sol"; interface IAMMWrapper is IStrategyBase { // Operator events @@ -23,8 +25,7 @@ interface IAMMWrapper is IStrategyBase { uint16 feeFactor ); - // Group the local variables together to prevent - // Compiler error: Stack too deep, try removing local variables. + // Group the local variables together to prevent stack too deep error. struct TxMetaData { string source; bytes32 transactionHash; @@ -35,16 +36,9 @@ interface IAMMWrapper is IStrategyBase { } function trade( - address _makerAddress, - address _fromAssetAddress, - address _toAssetAddress, - uint256 _takerAssetAmount, - uint256 _makerAssetAmount, + AMMLibEIP712.Order calldata _order, uint256 _feeFactor, - address _spender, - address payable _receiver, - uint256 _nonce, - uint256 _deadline, - bytes memory _sig + bytes calldata _sig, + bytes calldata _takerAssetPermitSig ) external payable returns (uint256); } diff --git a/contracts/interfaces/IAMMWrapperWithPath.sol b/contracts/interfaces/IAMMWrapperWithPath.sol index dd25122e..b034e82c 100644 --- a/contracts/interfaces/IAMMWrapperWithPath.sol +++ b/contracts/interfaces/IAMMWrapperWithPath.sol @@ -6,11 +6,15 @@ import "./IAMMWrapper.sol"; import "../utils/AMMLibEIP712.sol"; interface IAMMWrapperWithPath is IAMMWrapper { - function trade( - AMMLibEIP712.Order calldata _order, - uint256 _feeFactor, - bytes calldata _sig, - bytes calldata _makerSpecificData, - address[] calldata _path - ) external payable returns (uint256); + // Group the local variables together to prevent stack too deep error. + struct TradeWithPathParams { + AMMLibEIP712.Order order; + uint256 feeFactor; + bytes sig; + bytes takerAssetPermitSig; + bytes makerSpecificData; + address[] path; + } + + function trade(TradeWithPathParams calldata _params) external payable returns (uint256); } diff --git a/contracts/interfaces/IL2Deposit.sol b/contracts/interfaces/IL2Deposit.sol index 3324a0dc..0701cdbc 100644 --- a/contracts/interfaces/IL2Deposit.sol +++ b/contracts/interfaces/IL2Deposit.sol @@ -29,6 +29,7 @@ interface IL2Deposit is IStrategyBase { struct DepositParams { L2DepositLibEIP712.Deposit deposit; bytes depositSig; + bytes l1TokenPermitSig; } /// @notice Deposit user's fund into L2 bridge diff --git a/contracts/interfaces/IRFQ.sol b/contracts/interfaces/IRFQ.sol index 878d24c0..6a787ac1 100644 --- a/contracts/interfaces/IRFQ.sol +++ b/contracts/interfaces/IRFQ.sol @@ -4,6 +4,7 @@ pragma abicoder v2; import "./IStrategyBase.sol"; import "../utils/RFQLibEIP712.sol"; +import "../utils/SpenderLibEIP712.sol"; /// @title IRFQ Interface /// @author imToken Labs @@ -15,8 +16,10 @@ interface IRFQ is IStrategyBase { /// @param _userSignature The signature of the order from taker /// @return The settled amount of the order function fill( - RFQLibEIP712.Order memory _order, - bytes memory _mmSignature, - bytes memory _userSignature + RFQLibEIP712.Order calldata _order, + bytes calldata _mmSignature, + bytes calldata _userSignature, + bytes calldata _makerAssetPermitSig, + bytes calldata _takerAssetPermitSig ) external payable returns (uint256); } diff --git a/contracts/interfaces/ISpender.sol b/contracts/interfaces/ISpender.sol index b67d13ea..0cf5c36a 100644 --- a/contracts/interfaces/ISpender.sol +++ b/contracts/interfaces/ISpender.sol @@ -1,5 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0; +pragma abicoder v2; + +import "../utils/SpenderLibEIP712.sol"; interface ISpender { // System events @@ -12,16 +15,12 @@ interface ISpender { event BlackListToken(address token, bool isBlacklisted); event AuthorizeSpender(address spender, bool isAuthorized); - function spendFromUser( - address _user, - address _tokenAddr, - uint256 _amount - ) external; - function spendFromUserTo( address _user, address _tokenAddr, address _receiverAddr, uint256 _amount ) external; + + function spendFromUserToWithPermit(SpenderLibEIP712.SpendWithPermit calldata _params, bytes calldata _spendWithPermitSig) external; } diff --git a/contracts/test/RFQ.t.sol b/contracts/test/RFQ.t.sol index b5682d9b..840b07d1 100644 --- a/contracts/test/RFQ.t.sol +++ b/contracts/test/RFQ.t.sol @@ -8,18 +8,20 @@ import "@openzeppelin/contracts/math/SafeMath.sol"; import "contracts/MarketMakerProxy.sol"; import "contracts/RFQ.sol"; import "contracts/utils/SignatureValidator.sol"; +import "contracts/utils/LibConstant.sol"; import "contracts-test/mocks/MockERC1271Wallet.sol"; import "contracts-test/mocks/MockERC20.sol"; import "contracts-test/mocks/MockWETH.sol"; import "contracts-test/utils/BalanceSnapshot.sol"; import "contracts-test/utils/StrategySharedSetup.sol"; +import "contracts-test/utils/Permit.sol"; import { getEIP712Hash } from "contracts-test/utils/Sig.sol"; -contract RFQTest is StrategySharedSetup { +contract RFQTest is StrategySharedSetup, Permit { + using SafeMath for uint16; using SafeMath for uint256; using BalanceSnapshot for BalanceSnapshot.Snapshot; - uint256 constant BPS_MAX = 10000; event FillOrder( string source, bytes32 indexed transactionHash, @@ -250,8 +252,26 @@ contract RFQTest is StrategySharedSetup { order.deadline = block.timestamp - 1; bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); bytes memory userSig = _signFill({ privateKey: otherPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } vm.expectRevert("RFQ: expired order"); vm.prank(user, user); // Only EOA userProxy.toRFQ(payload); @@ -261,8 +281,26 @@ contract RFQTest is StrategySharedSetup { RFQLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); bytes memory userSig = _signFill({ privateKey: otherPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } vm.expectRevert("RFQ: invalid user signature"); vm.prank(user, user); // Only EOA userProxy.toRFQ(payload); @@ -273,8 +311,28 @@ contract RFQTest is StrategySharedSetup { bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); // Taker is an EOA but user signs a Wallet type fill bytes memory userSig = _signFill({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.WalletBytes32 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + // Sig with EIP712 type + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + // Sig with WalletBytes32 type + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.WalletBytes32 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } vm.expectRevert(); // No revert string in this case vm.prank(user, user); // Only EOA userProxy.toRFQ(payload); @@ -284,8 +342,26 @@ contract RFQTest is StrategySharedSetup { RFQLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory makerSig = _signOrder({ privateKey: otherPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); bytes memory userSig = _signFill({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } vm.expectRevert("RFQ: invalid MM signature"); vm.prank(user, user); // Only EOA userProxy.toRFQ(payload); @@ -295,8 +371,26 @@ contract RFQTest is StrategySharedSetup { RFQLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); bytes memory userSig = _signFill({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } BalanceSnapshot.Snapshot memory userTakerAsset = BalanceSnapshot.take({ owner: user, token: order.takerAssetAddr }); BalanceSnapshot.Snapshot memory receiverMakerAsset = BalanceSnapshot.take({ owner: receiver, token: order.makerAssetAddr }); BalanceSnapshot.Snapshot memory makerTakerAsset = BalanceSnapshot.take({ owner: maker, token: order.takerAssetAddr }); @@ -315,8 +409,26 @@ contract RFQTest is StrategySharedSetup { RFQLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory makerSig = _signOrderWithOldEIP712Method({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); bytes memory userSig = _signFillWithOldEIP712Method({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } BalanceSnapshot.Snapshot memory userTakerAsset = BalanceSnapshot.take({ owner: user, token: order.takerAssetAddr }); BalanceSnapshot.Snapshot memory receiverMakerAsset = BalanceSnapshot.take({ owner: receiver, token: order.makerAssetAddr }); BalanceSnapshot.Snapshot memory makerTakerAsset = BalanceSnapshot.take({ owner: maker, token: order.takerAssetAddr }); @@ -338,8 +450,28 @@ contract RFQTest is StrategySharedSetup { order.makerAddr = address(marketMakerProxy); bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.Wallet }); bytes memory userSig = _signFill({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + // Sig with Wallet type + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.Wallet + ); + // Sig with EIP712 type + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } BalanceSnapshot.Snapshot memory userTakerAsset = BalanceSnapshot.take({ owner: user, token: ETH_ADDRESS }); BalanceSnapshot.Snapshot memory receiverMakerAsset = BalanceSnapshot.take({ owner: receiver, token: order.makerAssetAddr }); BalanceSnapshot.Snapshot memory makerMMPTakerAsset = BalanceSnapshot.take({ owner: address(marketMakerProxy), token: order.takerAssetAddr }); @@ -362,8 +494,28 @@ contract RFQTest is StrategySharedSetup { order.makerAssetAmount = 1 ether; bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.Wallet }); bytes memory userSig = _signFill({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.WalletBytes32 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + // Sig with Wallet type + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.Wallet + ); + // Sig with WalletBytes32 type + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.WalletBytes32 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } BalanceSnapshot.Snapshot memory userWalletTakerAsset = BalanceSnapshot.take({ owner: address(mockERC1271Wallet), token: order.takerAssetAddr }); BalanceSnapshot.Snapshot memory receiverMakerAsset = BalanceSnapshot.take({ owner: receiver, token: ETH_ADDRESS }); BalanceSnapshot.Snapshot memory makerMMPTakerAsset = BalanceSnapshot.take({ owner: address(marketMakerProxy), token: order.takerAssetAddr }); @@ -383,8 +535,26 @@ contract RFQTest is StrategySharedSetup { order.feeFactor = 1000; // 10% fee bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); bytes memory userSig = _signFill({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } BalanceSnapshot.Snapshot memory userTakerAsset = BalanceSnapshot.take({ owner: user, token: order.takerAssetAddr }); BalanceSnapshot.Snapshot memory receiverMakerAsset = BalanceSnapshot.take({ owner: receiver, token: order.makerAssetAddr }); BalanceSnapshot.Snapshot memory makerTakerAsset = BalanceSnapshot.take({ owner: maker, token: order.takerAssetAddr }); @@ -405,8 +575,26 @@ contract RFQTest is StrategySharedSetup { RFQLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); bytes memory userSig = _signFill({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } vm.prank(user, user); // Only EOA userProxy.toRFQ(payload); @@ -432,7 +620,7 @@ contract RFQTest is StrategySharedSetup { order.makerAssetAddr, order.makerAssetAmount, order.receiverAddr, - order.makerAssetAmount.mul((BPS_MAX).sub(order.feeFactor)).div(BPS_MAX), + order.makerAssetAmount.mul((LibConstant.BPS_MAX).sub(order.feeFactor)).div(LibConstant.BPS_MAX), uint16(order.feeFactor) ); } @@ -441,8 +629,26 @@ contract RFQTest is StrategySharedSetup { RFQLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory makerSig = _signOrder({ privateKey: makerPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); bytes memory userSig = _signFill({ privateKey: userPrivateKey, order: order, sigType: SignatureValidator.SignatureType.EIP712 }); - bytes memory payload = _genFillPayload({ order: order, makerSig: makerSig, userSig: userSig }); - + bytes memory payload; // Bypass stack too deep error + { + ( + SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit + ) = _createSpenderPermitFromOrder(order); + bytes memory makerAssetPermitSig = signSpendWithPermit( + makerPrivateKey, + makerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genFillPayload(order, makerSig, userSig, makerAssetPermitSig, takerAssetPermitSig); + } _expectEvent(order); vm.prank(user, user); // Only EOA userProxy.toRFQ(payload); @@ -508,11 +714,42 @@ contract RFQTest is StrategySharedSetup { sig = abi.encodePacked(r, s, v, bytes32(0), uint8(sigType)); } + function _createSpenderPermitFromOrder(RFQLibEIP712.Order memory defaultOrder) + internal + view + returns (SpenderLibEIP712.SpendWithPermit memory makerAssetPermit, SpenderLibEIP712.SpendWithPermit memory takerAssetPermit) + { + // maker (= mm) order: -> taker recive maker's token but except fee + makerAssetPermit = SpenderLibEIP712.SpendWithPermit( + defaultOrder.makerAssetAddr, + address(rfq), + defaultOrder.makerAddr, + address(rfq), + defaultOrder.makerAssetAmount, + RFQLibEIP712._getOrderHash(defaultOrder), + uint64(defaultOrder.deadline) + ); + + // taker (= user) transaction (= fill): -> maker recive taker's token totally + takerAssetPermit = SpenderLibEIP712.SpendWithPermit( + defaultOrder.takerAssetAddr, + address(rfq), + defaultOrder.takerAddr, + address(rfq), + defaultOrder.takerAssetAmount, + RFQLibEIP712._getTransactionHash(defaultOrder), + uint64(defaultOrder.deadline) + ); + return (makerAssetPermit, takerAssetPermit); + } + function _genFillPayload( RFQLibEIP712.Order memory order, bytes memory makerSig, - bytes memory userSig + bytes memory userSig, + bytes memory spendMakerAssetToReceiverSig, + bytes memory spendTakerAssetToMakerSig ) internal view returns (bytes memory payload) { - return abi.encodeWithSelector(rfq.fill.selector, order, makerSig, userSig); + return abi.encodeWithSelector(rfq.fill.selector, order, makerSig, userSig, spendMakerAssetToReceiverSig, spendTakerAssetToMakerSig); } } diff --git a/contracts/test/Spender.t.sol b/contracts/test/Spender.t.sol index fa05ca7c..15fa18c8 100644 --- a/contracts/test/Spender.t.sol +++ b/contracts/test/Spender.t.sol @@ -1,39 +1,42 @@ // SPDX-License-Identifier: MIT pragma solidity 0.7.6; +pragma abicoder v2; import "forge-std/Test.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "contracts/Spender.sol"; +import "contracts/SpenderSimulation.sol"; import "contracts/AllowanceTarget.sol"; +import "contracts/utils/SpenderLibEIP712.sol"; +import "contracts/utils/SignatureValidator.sol"; import "contracts-test/mocks/MockERC20.sol"; import "contracts-test/mocks/MockDeflationaryERC20.sol"; import "contracts-test/mocks/MockNoReturnERC20.sol"; import "contracts-test/mocks/MockNoRevertERC20.sol"; import "contracts-test/utils/BalanceUtil.sol"; +import "contracts-test/utils/BalanceSnapshot.sol"; +import "contracts-test/utils/Permit.sol"; -contract SpenderTest is BalanceUtil { +contract SpenderTest is BalanceUtil, Permit { + using BalanceSnapshot for BalanceSnapshot.Snapshot; using SafeERC20 for IERC20; event TearDownAllowanceTarget(uint256 tearDownTimeStamp); - struct SpendWithPermit { - address tokenAddr; - address user; - address recipient; - uint256 amount; - uint256 salt; - uint64 expiry; - } uint256 userPrivateKey = uint256(1); uint256 otherPrivateKey = uint256(2); address user = vm.addr(userPrivateKey); + // Originally requester should be the address of the strategy contract + // that calls spender; But in this test case, the requester is address(this) + address requester = address(this); address recipient = address(0x133702); address unauthorized = address(0x133704); address[] wallet = [address(this), user, recipient]; Spender spender; + SpenderSimulation spenderSimulation; AllowanceTarget allowanceTarget; MockERC20 lon = new MockERC20("TOKENLON", "LON", 18); MockDeflationaryERC20 deflationaryERC20 = new MockDeflationaryERC20(); @@ -42,7 +45,7 @@ contract SpenderTest is BalanceUtil { IERC20[] tokens = [IERC20(address(deflationaryERC20)), IERC20(address(noReturnERC20)), IERC20(address(noRevertERC20))]; uint64 EXPIRY = uint64(block.timestamp + 1); - SpendWithPermit DEFAULT_SPEND_WITH_PERMIT; + SpenderLibEIP712.SpendWithPermit DEFAULT_SPEND_WITH_PERMIT; // effectively a "beforeEach" block function setUp() public { @@ -55,6 +58,11 @@ contract SpenderTest is BalanceUtil { // Setup spender.setAllowanceTarget(address(allowanceTarget)); spender.authorize(wallet); + // Deploy spenderSimulation contract and set its address to authorization list of spender + spenderSimulation = new SpenderSimulation(spender, new address[](0)); + address[] memory spenderSimulationAddr = new address[](1); + spenderSimulationAddr[0] = address(spenderSimulation); + spender.authorize(spenderSimulationAddr); // Deal 100 ETH to each account for (uint256 i = 0; i < wallet.length; i++) { @@ -74,21 +82,24 @@ contract SpenderTest is BalanceUtil { // Default SpendWithPermit // prettier-ignore - DEFAULT_SPEND_WITH_PERMIT = SpendWithPermit( + DEFAULT_SPEND_WITH_PERMIT = SpenderLibEIP712.SpendWithPermit( address(lon), // tokenAddr + requester, // requester user, // user recipient, // receipient 100 * 1e18, // amount - uint256(1234), // salt + bytes32(0x0), // actionHash EXPIRY // expiry ); // Label addresses for easier debugging + vm.label(requester, "Requester"); vm.label(user, "User"); vm.label(recipient, "Recipient"); vm.label(unauthorized, "Unauthorized"); vm.label(address(this), "TestingContract"); vm.label(address(spender), "SpenderContract"); + vm.label(address(spenderSimulation), "SpenderSimulationContract"); vm.label(address(allowanceTarget), "AllowanceTargetContract"); vm.label(address(lon), "LON"); } @@ -200,16 +211,16 @@ contract SpenderTest is BalanceUtil { } /********************************* - * Test: spendFromUser * + * Test: spendFromUserTo * *********************************/ - function testCannotSpendFromUserByNotAuthorized() public { + function testCannotSpendFromUserToByNotAuthorized() public { vm.expectRevert("Spender: not authorized"); vm.prank(unauthorized); - spender.spendFromUser(user, address(lon), 100); + spender.spendFromUserTo(user, address(lon), unauthorized, 100); } - function testCannotSpendFromUserWithBlakclistedToken() public { + function testCannotSpendFromUserToWithBlakclistedToken() public { address[] memory blacklistAddress = new address[](1); blacklistAddress[0] = address(lon); bool[] memory blacklistBool = new bool[](1); @@ -217,93 +228,165 @@ contract SpenderTest is BalanceUtil { spender.blacklist(blacklistAddress, blacklistBool); vm.expectRevert("Spender: token is blacklisted"); - spender.spendFromUser(user, address(lon), 100); + spender.spendFromUserTo(user, address(lon), recipient, 100); } - function testCannotSpendFromUserInsufficientBalance_NoReturnValueToken() public { + function testCannotSpendFromUserToInsufficientBalance_NoReturnValueToken() public { uint256 userBalance = noReturnERC20.balanceOf(user); vm.expectRevert("Spender: ERC20 transferFrom failed"); - spender.spendFromUser(user, address(noReturnERC20), userBalance + 1); + spender.spendFromUserTo(user, address(noReturnERC20), recipient, userBalance + 1); } - function testCannotSpendFromUserInsufficientBalance_ReturnFalseToken() public { + function testCannotSpendFromUserToInsufficientBalance_ReturnFalseToken() public { uint256 userBalance = noRevertERC20.balanceOf(user); vm.expectRevert("Spender: ERC20 transferFrom failed"); - spender.spendFromUser(user, address(noRevertERC20), userBalance + 1); + spender.spendFromUserTo(user, address(noRevertERC20), recipient, userBalance + 1); } - function testCannotSpendFromUserWithDeflationaryToken() public { + function testCannotSpendFromUserToWithDeflationaryToken() public { vm.expectRevert("Spender: ERC20 transferFrom amount mismatch"); - spender.spendFromUser(user, address(deflationaryERC20), 100); + spender.spendFromUserTo(user, address(deflationaryERC20), recipient, 100); + } + + function testSpendFromUserTo() public { + assertEq(lon.balanceOf(recipient), 0); + + spender.spendFromUserTo(user, address(lon), recipient, 100); + + assertEq(lon.balanceOf(recipient), 100); } - function testSpendFromUser() public { - assertEq(lon.balanceOf(address(this)), 0); + function testSpendFromUserToWithNoReturnValueToken() public { + assertEq(noReturnERC20.balanceOf(recipient), 0); - spender.spendFromUser(user, address(lon), 100); + spender.spendFromUserTo(user, address(noReturnERC20), recipient, 100); - assertEq(lon.balanceOf(address(this)), 100); + assertEq(noReturnERC20.balanceOf(recipient), 100); } - function testSpendFromUserWithNoReturnValueToken() public { - assertEq(noReturnERC20.balanceOf(address(this)), 0); + /******************************************* + * Test: spendFromUserToWithPermit * + *******************************************/ - spender.spendFromUser(user, address(noReturnERC20), 100); + function testCannotSpendFromUserToWithPermitWithExpiredPermit() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + spendWithPermit.expiry = uint64(block.timestamp - 1); // Timestamp expired + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); - assertEq(noReturnERC20.balanceOf(address(this)), 100); + vm.expectRevert("Spender: Permit is expired"); + spender.spendFromUserToWithPermit(spendWithPermit, sig); } - /********************************* - * Test: spendFromUserTo * - *********************************/ + function testCannotSpendFromUserToWithPermitWithWrongRequester() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + spendWithPermit.requester = unauthorized; // Wrong requester address + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); + + vm.expectRevert("Spender: invalid requester address"); + spender.spendFromUserToWithPermit(spendWithPermit, sig); + } + + function testCannotSpendFromUserToWithPermitByWrongSigner() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + bytes memory sig = signSpendWithPermit(otherPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); // Wrong signer + + vm.expectRevert("Spender: Invalid permit signature"); + spender.spendFromUserToWithPermit(spendWithPermit, sig); + } + + function testCannotSpendFromUserToWithPermitWithWrongRecipient() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); + + vm.expectRevert("Spender: Invalid permit signature"); + spendWithPermit.recipient = unauthorized; // recipient is different from signed + spender.spendFromUserToWithPermit(spendWithPermit, sig); + } + + function testCannotSpendFromUserToWithPermitWithWrongToken() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); + + vm.expectRevert("Spender: Invalid permit signature"); + spendWithPermit.tokenAddr = unauthorized; // tokenAddr is different from signed + spender.spendFromUserToWithPermit(spendWithPermit, sig); + } + + function testCannotSpendFromUserToWithPermitWithWrongAmount() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); + + vm.expectRevert("Spender: Invalid permit signature"); + spendWithPermit.amount = spendWithPermit.amount + 1; // amount is different from signed + spender.spendFromUserToWithPermit(spendWithPermit, sig); + } + + function testCannotSpendFromUserToWithPermitByNotAuthorized() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); - function testCannotSpendFromUserToByNotAuthorized() public { vm.expectRevert("Spender: not authorized"); - vm.prank(unauthorized); - spender.spendFromUserTo(user, address(lon), unauthorized, 100); + vm.prank(unauthorized); // Only authorized strategy contracts and owner + spender.spendFromUserToWithPermit(spendWithPermit, sig); } - function testCannotSpendFromUserToWithBlakclistedToken() public { + function testCannotSpendFromUserToWithPermitWithBlacklistedToken() public { address[] memory blacklistAddress = new address[](1); blacklistAddress[0] = address(lon); bool[] memory blacklistBool = new bool[](1); blacklistBool[0] = true; - spender.blacklist(blacklistAddress, blacklistBool); + spender.blacklist(blacklistAddress, blacklistBool); // Set lon to black list by owner (this contract) + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); vm.expectRevert("Spender: token is blacklisted"); - spender.spendFromUserTo(user, address(lon), recipient, 100); + spender.spendFromUserToWithPermit(spendWithPermit, sig); } - function testCannotSpendFromUserToInsufficientBalance_NoReturnValueToken() public { - uint256 userBalance = noReturnERC20.balanceOf(user); - vm.expectRevert("Spender: ERC20 transferFrom failed"); - spender.spendFromUserTo(user, address(noReturnERC20), recipient, userBalance + 1); - } + function testCannotSpendFromUserToWithPermitWithSamePermit() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); + spender.spendFromUserToWithPermit(spendWithPermit, sig); - function testCannotSpendFromUserToInsufficientBalance_ReturnFalseToken() public { - uint256 userBalance = noRevertERC20.balanceOf(user); - vm.expectRevert("Spender: ERC20 transferFrom failed"); - spender.spendFromUserTo(user, address(noRevertERC20), recipient, userBalance + 1); + vm.expectRevert("Spender: Permit is already fulfilled"); + spender.spendFromUserToWithPermit(spendWithPermit, sig); // Detected the same permit hash in the past } - function testCannotSpendFromUserToWithDeflationaryToken() public { - vm.expectRevert("Spender: ERC20 transferFrom amount mismatch"); - spender.spendFromUserTo(user, address(deflationaryERC20), recipient, 100); - } + function testSpendFromUserToWithPermit() public { + BalanceSnapshot.Snapshot memory recipientLon = BalanceSnapshot.take(recipient, address(lon)); + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); + spender.spendFromUserToWithPermit(spendWithPermit, sig); - function testSpendFromUserTo() public { - assertEq(lon.balanceOf(recipient), 0); + recipientLon.assertChange(int256(spendWithPermit.amount)); // Confirm amount of tokens received + } - spender.spendFromUserTo(user, address(lon), recipient, 100); + /******************************************* + * Test: simulate * + *******************************************/ + + function testCannotSimulateByNotAuthorized() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + // Set the requester address to spenderSimulation contract + spendWithPermit.requester = address(spenderSimulation); + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); + // Set address of spenderSimulation to be removed from authorization list of spender, + // and spenderSimulation can not execute spender.spendFromUserToWithPermit() function. + address[] memory spenderSimulationAddr = new address[](1); + spenderSimulationAddr[0] = address(spenderSimulation); + spender.deauthorize(spenderSimulationAddr); - assertEq(lon.balanceOf(recipient), 100); + vm.expectRevert("Spender: not authorized"); + spenderSimulation.simulate(spendWithPermit, sig); } - function testSpendFromUserToWithNoReturnValueToken() public { - assertEq(noReturnERC20.balanceOf(recipient), 0); - - spender.spendFromUserTo(user, address(noReturnERC20), recipient, 100); + function testSimulate() public { + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = DEFAULT_SPEND_WITH_PERMIT; + // Set the requester address to spenderSimulation contract + spendWithPermit.requester = address(spenderSimulation); + bytes memory sig = signSpendWithPermit(userPrivateKey, spendWithPermit, spender.EIP712_DOMAIN_SEPARATOR(), SignatureValidator.SignatureType.EIP712); - assertEq(noReturnERC20.balanceOf(recipient), 100); + vm.expectRevert("SpenderSimulation: transfer simulation success"); + spenderSimulation.simulate(spendWithPermit, sig); } } diff --git a/contracts/test/forkMainnet/AMMWrapper/CollectFee.t.sol b/contracts/test/forkMainnet/AMMWrapper/CollectFee.t.sol index b5870fd0..1b498f5b 100644 --- a/contracts/test/forkMainnet/AMMWrapper/CollectFee.t.sol +++ b/contracts/test/forkMainnet/AMMWrapper/CollectFee.t.sol @@ -16,8 +16,17 @@ contract TestAMMWrapperCollectFee is TestAMMWrapper { uint256 expectedOutAmount = ammQuoter.getMakerOutAmount(order.makerAddr, order.takerAssetAddr, order.makerAssetAddr, order.takerAssetAmount); order.makerAssetAmount = expectedOutAmount; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, feeFactor, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, feeFactor, sig, takerAssetPermitSig); + } vm.expectRevert("UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"); vm.prank(relayer, relayer); userProxy.toAMM(payload); @@ -33,8 +42,17 @@ contract TestAMMWrapperCollectFee is TestAMMWrapper { // therefore it should deduct fee from expectedOutAmount as the makerAssetAmount in order order.makerAssetAmount = settleAmount; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, feeFactor, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, feeFactor, sig, takerAssetPermitSig); + } BalanceSnapshot.Snapshot memory feeCollectorMakerAsset = BalanceSnapshot.take(feeCollector, order.makerAssetAddr); vm.prank(relayer, relayer); @@ -54,8 +72,17 @@ contract TestAMMWrapperCollectFee is TestAMMWrapper { // therefore it should deduct fee from expectedOutAmount as the makerAssetAmount in order order.makerAssetAmount = settleAmount; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, feeFactor, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, feeFactor, sig, takerAssetPermitSig); + } address feeTokenAddress = WETH_ADDRESS; // AMM other than Curve returns WETH instead of ETH BalanceSnapshot.Snapshot memory feeCollectorMakerAsset = BalanceSnapshot.take(feeCollector, feeTokenAddress); @@ -79,8 +106,17 @@ contract TestAMMWrapperCollectFee is TestAMMWrapper { // therefore it should deduct fee from expectedOutAmount as the makerAssetAmount in order order.makerAssetAmount = settleAmount; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, feeFactor, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, feeFactor, sig, takerAssetPermitSig); + } address feeTokenAddress = ETH_ADDRESS; // Curve ETH/ANKRETH pool returns ETH instead of WETH BalanceSnapshot.Snapshot memory feeCollectorMakerAsset = BalanceSnapshot.take(feeCollector, feeTokenAddress); @@ -104,7 +140,17 @@ contract TestAMMWrapperCollectFee is TestAMMWrapper { } bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, feeFactor, sig); + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, feeFactor, sig, takerAssetPermitSig); + } uint256 actualFee = (expectedOutAmount * DEFAULT_FEE_FACTOR) / LibConstant.BPS_MAX; BalanceSnapshot.Snapshot memory feeCollectorMakerAsset = BalanceSnapshot.take(feeCollector, order.makerAssetAddr); diff --git a/contracts/test/forkMainnet/AMMWrapper/Setup.t.sol b/contracts/test/forkMainnet/AMMWrapper/Setup.t.sol index d9eeca94..831ddc11 100644 --- a/contracts/test/forkMainnet/AMMWrapper/Setup.t.sol +++ b/contracts/test/forkMainnet/AMMWrapper/Setup.t.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.7.6; +pragma abicoder v2; import "contracts/AMMWrapper.sol"; import "contracts/AMMQuoter.sol"; @@ -7,10 +8,10 @@ import "contracts/interfaces/IPermanentStorage.sol"; import "contracts/interfaces/ISpender.sol"; import "contracts/utils/AMMLibEIP712.sol"; import "contracts-test/utils/StrategySharedSetup.sol"; // Using the deployment Strategy Contract function +import "contracts-test/utils/Permit.sol"; import { getEIP712Hash } from "contracts-test/utils/Sig.sol"; -contract TestAMMWrapper is StrategySharedSetup { - uint256 constant BPS_MAX = 10000; +contract TestAMMWrapper is StrategySharedSetup, Permit { bytes32 public constant relayerValidStorageId = 0x2c97779b4deaf24e9d46e02ec2699240a957d92782b51165b93878b09dd66f61; // keccak256("relayerValid") uint256 userPrivateKey = uint256(1); @@ -62,17 +63,17 @@ contract TestAMMWrapper is StrategySharedSetup { setEOABalanceAndApprove(user, tokens, uint256(100)); // Default order - DEFAULT_ORDER = AMMLibEIP712.Order( - UNISWAP_V2_ADDRESS, // makerAddr - address(dai), // takerAssetAddr - address(usdt), // makerAssetAddr - uint256(100 * 1e18), // takerAssetAmount - uint256(90 * 1e6), // makerAssetAmount - user, // userAddr - payable(user), // receiverAddr - uint256(1234), // salt - DEADLINE // deadline - ); + DEFAULT_ORDER = AMMLibEIP712.Order({ + makerAddr: UNISWAP_V2_ADDRESS, + takerAssetAddr: address(dai), + makerAssetAddr: address(usdt), + takerAssetAmount: uint256(100 * 1e18), + makerAssetAmount: uint256(90 * 1e6), + userAddr: user, + receiverAddr: payable(user), + salt: uint256(1234), + deadline: DEADLINE + }); // Label addresses for easier debugging vm.label(user, "User"); @@ -134,25 +135,25 @@ contract TestAMMWrapper is StrategySharedSetup { sig = abi.encodePacked(r, s, v, bytes32(0), uint8(2)); } + function _createSpenderPermitFromOrder(AMMLibEIP712.Order memory order) internal view returns (SpenderLibEIP712.SpendWithPermit memory takerAssetPermit) { + takerAssetPermit = SpenderLibEIP712.SpendWithPermit({ + tokenAddr: order.takerAssetAddr, + requester: address(ammWrapper), + user: order.userAddr, + recipient: address(ammWrapper), + amount: order.takerAssetAmount, + actionHash: AMMLibEIP712._getOrderHash(order), + expiry: uint64(order.deadline) + }); + return takerAssetPermit; + } + function _genTradePayload( AMMLibEIP712.Order memory order, uint256 feeFactor, - bytes memory sig + bytes memory sig, + bytes memory takerAssetPermitSig ) internal pure returns (bytes memory payload) { - return - abi.encodeWithSignature( - "trade(address,address,address,uint256,uint256,uint256,address,address,uint256,uint256,bytes)", - order.makerAddr, - order.takerAssetAddr, - order.makerAssetAddr, - order.takerAssetAmount, - order.makerAssetAmount, - feeFactor, - order.userAddr, - order.receiverAddr, - order.salt, - order.deadline, - sig - ); + return abi.encodeWithSelector(AMMWrapper.trade.selector, order, feeFactor, sig, takerAssetPermitSig); } } diff --git a/contracts/test/forkMainnet/AMMWrapper/TradeCurveV1.t.sol b/contracts/test/forkMainnet/AMMWrapper/TradeCurveV1.t.sol index a31e2302..53763145 100644 --- a/contracts/test/forkMainnet/AMMWrapper/TradeCurveV1.t.sol +++ b/contracts/test/forkMainnet/AMMWrapper/TradeCurveV1.t.sol @@ -12,8 +12,17 @@ contract TestAMMWrapperTradeCurveV1 is TestAMMWrapper { AMMLibEIP712.Order memory order = DEFAULT_ORDER; order.makerAddr = CURVE_USDT_POOL_ADDRESS; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, feeFactor, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, feeFactor, sig, takerAssetPermitSig); + } BalanceSnapshot.Snapshot memory userTakerAsset = BalanceSnapshot.take(user, order.takerAssetAddr); BalanceSnapshot.Snapshot memory userMakerAsset = BalanceSnapshot.take(user, order.makerAssetAddr); diff --git a/contracts/test/forkMainnet/AMMWrapper/TradeSushiswap.t.sol b/contracts/test/forkMainnet/AMMWrapper/TradeSushiswap.t.sol index 50f2cb78..a63c9900 100644 --- a/contracts/test/forkMainnet/AMMWrapper/TradeSushiswap.t.sol +++ b/contracts/test/forkMainnet/AMMWrapper/TradeSushiswap.t.sol @@ -13,8 +13,17 @@ contract TestAMMWrapperTradeSushiswap is TestAMMWrapper { order.makerAssetAddr = ETH_ADDRESS; order.makerAssetAmount = 0.001 ether; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmount(order.makerAddr, order.takerAssetAddr, order.makerAssetAddr, order.takerAssetAmount); uint256 actualFee = (expectedOutAmount * DEFAULT_FEE_FACTOR) / LibConstant.BPS_MAX; uint256 settleAmount = expectedOutAmount - actualFee; diff --git a/contracts/test/forkMainnet/AMMWrapper/TradeUniswapV2.t.sol b/contracts/test/forkMainnet/AMMWrapper/TradeUniswapV2.t.sol index 5af97965..ec930f9c 100644 --- a/contracts/test/forkMainnet/AMMWrapper/TradeUniswapV2.t.sol +++ b/contracts/test/forkMainnet/AMMWrapper/TradeUniswapV2.t.sol @@ -10,8 +10,17 @@ contract TestAMMWrapperTradeUniswapV2 is TestAMMWrapper { function testCannotTradeWithInvalidSignature() public { AMMLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory sig = _signTrade(otherPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig); + } vm.expectRevert("AMMWrapper: invalid user signature"); userProxy.toAMM(payload); } @@ -19,8 +28,17 @@ contract TestAMMWrapperTradeUniswapV2 is TestAMMWrapper { function testCannotTradeWhenPayloadSeenBefore() public { AMMLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig); + } userProxy.toAMM(payload); vm.expectRevert("PermanentStorage: transaction seen before"); @@ -32,8 +50,17 @@ contract TestAMMWrapperTradeUniswapV2 is TestAMMWrapper { order.takerAssetAddr = ETH_ADDRESS; order.takerAssetAmount = 0.1 ether; bytes memory sig = _signTradeWithOldEIP712Method(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmount(order.makerAddr, order.takerAssetAddr, order.makerAssetAddr, order.takerAssetAmount); uint256 actualFee = (expectedOutAmount * DEFAULT_FEE_FACTOR) / LibConstant.BPS_MAX; uint256 settleAmount = expectedOutAmount - actualFee; @@ -55,8 +82,17 @@ contract TestAMMWrapperTradeUniswapV2 is TestAMMWrapper { order.takerAssetAddr = ETH_ADDRESS; order.takerAssetAmount = 0.1 ether; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmount(order.makerAddr, order.takerAssetAddr, order.makerAssetAddr, order.takerAssetAmount); uint256 actualFee = (expectedOutAmount * DEFAULT_FEE_FACTOR) / LibConstant.BPS_MAX; uint256 settleAmount = expectedOutAmount - actualFee; @@ -76,8 +112,17 @@ contract TestAMMWrapperTradeUniswapV2 is TestAMMWrapper { function testEmitSwappedEvent() public { AMMLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmount(order.makerAddr, order.takerAssetAddr, order.makerAssetAddr, order.takerAssetAmount); uint256 actualFee = (expectedOutAmount * DEFAULT_FEE_FACTOR) / LibConstant.BPS_MAX; uint256 settleAmount = expectedOutAmount - actualFee; diff --git a/contracts/test/forkMainnet/AMMWrapperWithPath/Setup.t.sol b/contracts/test/forkMainnet/AMMWrapperWithPath/Setup.t.sol index 88a8d52e..390d2955 100644 --- a/contracts/test/forkMainnet/AMMWrapperWithPath/Setup.t.sol +++ b/contracts/test/forkMainnet/AMMWrapperWithPath/Setup.t.sol @@ -7,10 +7,11 @@ import "contracts/interfaces/IPermanentStorage.sol"; import "contracts/utils/AMMLibEIP712.sol"; import "contracts-test/utils/UniswapV3Util.sol"; import "contracts-test/utils/StrategySharedSetup.sol"; // Using the deployment Strategy Contract function +import "contracts-test/utils/Permit.sol"; import { getEIP712Hash } from "contracts-test/utils/Sig.sol"; import "contracts/AMMQuoter.sol"; -contract TestAMMWrapperWithPath is StrategySharedSetup { +contract TestAMMWrapperWithPath is StrategySharedSetup, Permit { using SafeERC20 for IERC20; uint256 userPrivateKey = uint256(1); uint256 otherPrivateKey = uint256(2); @@ -71,17 +72,17 @@ contract TestAMMWrapperWithPath is StrategySharedSetup { setEOABalanceAndApprove(user, tokens, uint256(100)); // Default order - DEFAULT_ORDER = AMMLibEIP712.Order( - UNISWAP_V3_ADDRESS, // makerAddr - address(usdc), // takerAssetAddr - address(dai), // makerAssetAddr - uint256(100 * 1e6), // takerAssetAmount - uint256(90 * 1e18), // makerAssetAmount - user, // userAddr - payable(user), // receiverAddr - uint256(1234), // salt - DEADLINE // deadline - ); + DEFAULT_ORDER = AMMLibEIP712.Order({ + makerAddr: UNISWAP_V3_ADDRESS, + takerAssetAddr: address(usdc), + makerAssetAddr: address(dai), + takerAssetAmount: uint256(100 * 1e6), + makerAssetAmount: uint256(90 * 1e18), + userAddr: user, + receiverAddr: payable(user), + salt: uint256(1234), + deadline: DEADLINE + }); DEFAULT_MULTI_HOP_PATH = new address[](3); DEFAULT_MULTI_HOP_PATH[0] = DEFAULT_ORDER.takerAssetAddr; DEFAULT_MULTI_HOP_PATH[1] = address(weth); @@ -155,21 +156,35 @@ contract TestAMMWrapperWithPath is StrategySharedSetup { sig = abi.encodePacked(r, s, v, bytes32(0), uint8(2)); } + function _createSpenderPermitFromOrder(AMMLibEIP712.Order memory order) internal view returns (SpenderLibEIP712.SpendWithPermit memory takerAssetPermit) { + takerAssetPermit = SpenderLibEIP712.SpendWithPermit({ + tokenAddr: order.takerAssetAddr, + requester: address(ammWrapperWithPath), + user: order.userAddr, + recipient: address(ammWrapperWithPath), + amount: order.takerAssetAmount, + actionHash: AMMLibEIP712._getOrderHash(order), + expiry: uint64(order.deadline) + }); + return takerAssetPermit; + } + function _genTradePayload( AMMLibEIP712.Order memory order, uint256 feeFactor, bytes memory sig, + bytes memory takerAssetPermitSig, bytes memory makerSpecificData, address[] memory path ) internal pure returns (bytes memory payload) { - return - abi.encodeWithSignature( - "trade((address,address,address,uint256,uint256,address,address,uint256,uint256),uint256,bytes,bytes,address[])", - order, - feeFactor, - sig, - makerSpecificData, - path - ); + IAMMWrapperWithPath.TradeWithPathParams memory params = IAMMWrapperWithPath.TradeWithPathParams( + order, + feeFactor, + sig, + takerAssetPermitSig, + makerSpecificData, + path + ); + return abi.encodeWithSelector(AMMWrapperWithPath.trade.selector, params); } } diff --git a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeBalancerV2.t.sol b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeBalancerV2.t.sol index a4ac91e4..b621b7da 100644 --- a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeBalancerV2.t.sol +++ b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeBalancerV2.t.sol @@ -18,8 +18,17 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { path[0] = order.takerAssetAddr; path[1] = order.makerAssetAddr; IBalancerV2Vault.BatchSwapStep[] memory swapSteps = new IBalancerV2Vault.BatchSwapStep[](0); // Empty SwapSteps - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } vm.expectRevert("AMMWrapper: BalancerV2 requires at least one swap step"); userProxy.toAMM(payload); } @@ -37,8 +46,17 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { order.takerAssetAmount, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } vm.expectRevert("AMMWrapper: path length must be at least two"); userProxy.toAMM(payload); } @@ -60,8 +78,18 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { order.takerAssetAmount, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); - + bytes memory payload; // Bypass stack too deep error + bytes memory takerAssetPermitSig; + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } vm.expectRevert("AMMWrapper: BalancerV2 first step asset in should match taker asset"); userProxy.toAMM(payload); @@ -69,7 +97,7 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { swapSteps[0].poolId = BALANCER_WETH_DAI_POOL; swapSteps[0].assetInIndex = 0; swapSteps[0].assetOutIndex = 1; - payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); vm.expectRevert("AMMWrapper: BalancerV2 last step asset out should match maker asset"); userProxy.toAMM(payload); @@ -90,8 +118,17 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { order.takerAssetAmount, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, order.takerAssetAddr, @@ -135,15 +172,25 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { 0, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); - + bytes memory payload; // Bypass stack too deep error + bytes memory takerAssetPermitSig; + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } vm.expectRevert("AMMWrapper: BalancerV2 cannot swap more than taker asset amount"); userProxy.toAMM(payload); uint256 invalidOtherSwapStepAssetInAmount = 999; // Amount of other SwapSteps must be zero swapSteps[0].amount = order.takerAssetAmount; // Restore amount of first SwapStep swapSteps[1].amount = invalidOtherSwapStepAssetInAmount; - payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); vm.expectRevert("AMMWrapper: BalancerV2 can only specify amount at first step"); userProxy.toAMM(payload); @@ -169,8 +216,17 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { 0, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, order.takerAssetAddr, @@ -211,8 +267,17 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { order.takerAssetAmount, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } // Balancer shoud revert with BAL#521, which means Token is not register vm.expectRevert("BAL#521"); userProxy.toAMM(payload); @@ -236,8 +301,17 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { order.takerAssetAmount, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeBalancerData(swapSteps), path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } // Balancer shoud revert with BAL#521, which means Token is not register vm.expectRevert("BAL#521"); userProxy.toAMM(payload); @@ -257,7 +331,19 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { order.takerAssetAmount, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, _signTrade(userPrivateKey, order), _encodeBalancerData(swapSteps), path); + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, _signTrade(userPrivateKey, order), takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } + // More stack than the one curly bracket can handle, + // use the second curly bracket to bypass stack too deep error { uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, @@ -308,7 +394,19 @@ contract TestAMMWrapperWithPathTradeBalancerV2 is TestAMMWrapperWithPath { 0, // amount new bytes(0) // userData ); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, _signTrade(userPrivateKey, order), _encodeBalancerData(swapSteps), path); + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, _signTrade(userPrivateKey, order), takerAssetPermitSig, _encodeBalancerData(swapSteps), path); + } + // More stack than the one curly bracket can handle, + // use the second curly bracket to bypass stack too deep error { uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, diff --git a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeCurveV1.t.sol b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeCurveV1.t.sol index 8b7aaaab..d0a7fbdb 100644 --- a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeCurveV1.t.sol +++ b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeCurveV1.t.sol @@ -15,8 +15,17 @@ contract TestAMMWrapperWithPathTradeCurveV1 is TestAMMWrapperWithPath { bytes memory sig = _signTrade(userPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = _encodeCurveData(1); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } BalanceSnapshot.Snapshot memory userTakerAsset = BalanceSnapshot.take(user, order.takerAssetAddr); BalanceSnapshot.Snapshot memory userMakerAsset = BalanceSnapshot.take(user, order.makerAssetAddr); diff --git a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeCurveV2.t.sol b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeCurveV2.t.sol index 5077978a..4bc902d1 100644 --- a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeCurveV2.t.sol +++ b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeCurveV2.t.sol @@ -17,8 +17,17 @@ contract TestAMMWrapperWithPathTradeCurveV2 is TestAMMWrapperWithPath { order.makerAssetAddr = address(wbtc); order.makerAssetAmount = 0.001 * 1e8; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeCurveData(2), new address[](0)); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeCurveData(2), new address[](0)); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, order.takerAssetAddr, @@ -47,8 +56,18 @@ contract TestAMMWrapperWithPathTradeCurveV2 is TestAMMWrapperWithPath { order.makerAddr = CURVE_USDT_POOL_ADDRESS; bytes memory sig = _signTrade(userPrivateKey, order); // Curve USDT pool is version 1 but we input version 2 - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeCurveData(2), new address[](0)); - + bytes memory payload; // Bypass stack too deep error + bytes memory takerAssetPermitSig; + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeCurveData(2), new address[](0)); + } vm.expectRevert("AMMWrapper: Curve v2 no underlying"); userProxy.toAMM(payload); @@ -58,7 +77,7 @@ contract TestAMMWrapperWithPathTradeCurveV2 is TestAMMWrapperWithPath { order.takerAssetAddr = USDC_ADDRESS; order.makerAddr = CURVE_USDT_POOL_ADDRESS; sig = _signTrade(userPrivateKey, order); - payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeCurveData(2), new address[](0)); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeCurveData(2), new address[](0)); vm.expectRevert("AMMWrapper: Curve v2 no underlying"); userProxy.toAMM(payload); @@ -70,8 +89,17 @@ contract TestAMMWrapperWithPathTradeCurveV2 is TestAMMWrapperWithPath { // give an unpsorted token to swap order.takerAssetAddr = LON_ADDRESS; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeCurveData(2), new address[](0)); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeCurveData(2), new address[](0)); + } vm.expectRevert("PermanentStorage: invalid pair"); userProxy.toAMM(payload); } @@ -83,7 +111,17 @@ contract TestAMMWrapperWithPathTradeCurveV2 is TestAMMWrapperWithPath { order.makerAssetAddr = address(wbtc); bytes memory sig = _signTrade(userPrivateKey, order); // curve doesn't has v3 - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeCurveData(3), new address[](0)); + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeCurveData(3), new address[](0)); + } vm.expectRevert("AMMWrapper: Invalid Curve version"); userProxy.toAMM(payload); } @@ -97,8 +135,17 @@ contract TestAMMWrapperWithPathTradeCurveV2 is TestAMMWrapperWithPath { order.makerAssetAmount = 0; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeCurveData(2), new address[](0)); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeCurveData(2), new address[](0)); + } vm.expectRevert(); userProxy.toAMM(payload); } @@ -113,8 +160,19 @@ contract TestAMMWrapperWithPathTradeCurveV2 is TestAMMWrapperWithPath { address[] memory path = new address[](2); bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeCurveData(2), path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeCurveData(2), path); + } + // More stack than the one curly bracket can handle, + // use the second curly bracket to bypass stack too deep error { uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, @@ -154,8 +212,17 @@ contract TestAMMWrapperWithPathTradeCurveV2 is TestAMMWrapperWithPath { order.makerAssetAddr = address(wbtc); order.makerAssetAmount = 1000 * 1e8; // unlikely to fill this amount bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, _encodeCurveData(2), new address[](0)); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, _encodeCurveData(2), new address[](0)); + } vm.expectRevert("Slippage"); userProxy.toAMM(payload); } diff --git a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeSushiswap.t.sol b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeSushiswap.t.sol index 47b2641f..190a9154 100644 --- a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeSushiswap.t.sol +++ b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeSushiswap.t.sol @@ -16,8 +16,17 @@ contract TestAMMWrapperWithPathTradeSushiswap is TestAMMWrapperWithPath { bytes memory sig = _signTrade(userPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = bytes(""); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmount(order.makerAddr, order.takerAssetAddr, order.makerAssetAddr, order.takerAssetAmount); uint256 actualFee = (expectedOutAmount * DEFAULT_FEE_FACTOR) / LibConstant.BPS_MAX; uint256 settleAmount = expectedOutAmount - actualFee; @@ -42,8 +51,17 @@ contract TestAMMWrapperWithPathTradeSushiswap is TestAMMWrapperWithPath { bytes memory sig = _signTradeWithOldEIP712Method(userPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = bytes(""); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmount(order.makerAddr, order.takerAssetAddr, order.makerAssetAddr, order.takerAssetAmount); uint256 actualFee = (expectedOutAmount * DEFAULT_FEE_FACTOR) / LibConstant.BPS_MAX; uint256 settleAmount = expectedOutAmount - actualFee; @@ -70,8 +88,17 @@ contract TestAMMWrapperWithPathTradeSushiswap is TestAMMWrapperWithPath { path[1] = address(dai); path[2] = address(weth); bytes memory makerSpecificData = bytes(""); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, order.takerAssetAddr, diff --git a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeUniswapV3.t.sol b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeUniswapV3.t.sol index b5011ee5..144d4f2f 100644 --- a/contracts/test/forkMainnet/AMMWrapperWithPath/TradeUniswapV3.t.sol +++ b/contracts/test/forkMainnet/AMMWrapperWithPath/TradeUniswapV3.t.sol @@ -18,8 +18,17 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { bytes memory sig = _signTrade(otherPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = bytes(""); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } vm.expectRevert("AMMWrapper: invalid user signature"); userProxy.toAMM(payload); } @@ -29,13 +38,23 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { bytes memory sig = _signTrade(userPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = _encodeUniswapSinglePoolData(UNSUPPORTED_SWAP_TYPE, FEE_MEDIUM); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + bytes memory takerAssetPermitSig; + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } vm.expectRevert("AMMWrapper: unsupported UniswapV3 swap type"); userProxy.toAMM(payload); makerSpecificData = _encodeUniswapSinglePoolData(INVALID_SWAP_TYPE, FEE_MEDIUM); - payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); // No revert string as it violates SwapType enum's sanity check vm.expectRevert(); @@ -47,14 +66,24 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { bytes memory sig = _signTrade(userPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = _encodeUniswapSinglePoolData(SINGLE_POOL_SWAP_TYPE, INVALID_ZERO_FEE); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + bytes memory takerAssetPermitSig; + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } // No revert string for invalid pool fee vm.expectRevert(); userProxy.toAMM(payload); makerSpecificData = _encodeUniswapSinglePoolData(SINGLE_POOL_SWAP_TYPE, INVALID_OVER_FEE); - payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); // No revert string for invalid pool fee vm.expectRevert(); @@ -67,8 +96,18 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { address[] memory path = DEFAULT_MULTI_HOP_PATH; bytes memory garbageData = new bytes(0); bytes memory makerSpecificData = abi.encode(MULTI_POOL_SWAP_TYPE, garbageData); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + bytes memory takerAssetPermitSig; + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } vm.expectRevert("toAddress_outOfBounds"); userProxy.toAMM(payload); @@ -76,7 +115,7 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { garbageData[0] = "5"; garbageData[1] = "5"; makerSpecificData = abi.encode(MULTI_POOL_SWAP_TYPE, garbageData); // Update the path variable - payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); vm.expectRevert("toAddress_outOfBounds"); userProxy.toAMM(payload); @@ -89,8 +128,17 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { path[0] = order.takerAssetAddr; uint24[] memory fees = new uint24[](0); // No fees specified bytes memory makerSpecificData = _encodeUniswapMultiPoolData(MULTI_POOL_SWAP_TYPE, path, fees); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } vm.expectRevert("toUint24_outOfBounds"); userProxy.toAMM(payload); } @@ -102,15 +150,26 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { path[0] = user; uint24[] memory fees = DEFAULT_MULTI_HOP_POOL_FEES; bytes memory makerSpecificData = _encodeUniswapMultiPoolData(MULTI_POOL_SWAP_TYPE, path, fees); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + bytes memory takerAssetPermitSig; + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } vm.expectRevert("UniswapV3: first element of path must match token in"); userProxy.toAMM(payload); path = DEFAULT_MULTI_HOP_PATH; path[2] = user; makerSpecificData = _encodeUniswapMultiPoolData(MULTI_POOL_SWAP_TYPE, path, fees); // Update the path variable - payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + vm.expectRevert("UniswapV3: last element of path must match token out"); userProxy.toAMM(payload); } @@ -120,8 +179,17 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { bytes memory sig = _signTrade(userPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = _encodeUniswapSinglePoolData(SINGLE_POOL_SWAP_TYPE, FEE_MEDIUM); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } userProxy.toAMM(payload); vm.expectRevert("PermanentStorage: transaction seen before"); @@ -148,8 +216,17 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { order.makerAssetAmount = settleAmount + 1; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } vm.prank(relayer, relayer); vm.expectRevert("Too little received"); userProxy.toAMM(payload); @@ -162,8 +239,17 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { bytes memory sig = _signTrade(userPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = _encodeUniswapSinglePoolData(SINGLE_POOL_SWAP_TYPE, FEE_MEDIUM); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, order.takerAssetAddr, @@ -192,8 +278,17 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { bytes memory sig = _signTrade(userPrivateKey, order); address[] memory path = new address[](0); bytes memory makerSpecificData = _encodeUniswapSinglePoolData(SINGLE_POOL_SWAP_TYPE, FEE_MEDIUM); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, order.takerAssetAddr, @@ -223,8 +318,17 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { address[] memory path = DEFAULT_MULTI_HOP_PATH; uint24[] memory fees = DEFAULT_MULTI_HOP_POOL_FEES; bytes memory makerSpecificData = _encodeUniswapMultiPoolData(MULTI_POOL_SWAP_TYPE, path, fees); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, order.takerAssetAddr, @@ -269,8 +373,17 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { order.makerAssetAmount = settleAmount; bytes memory sig = _signTrade(userPrivateKey, order); - bytes memory payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, makerSpecificData, path); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, DEFAULT_FEE_FACTOR, sig, takerAssetPermitSig, makerSpecificData, path); + } BalanceSnapshot.Snapshot memory userTakerAsset = BalanceSnapshot.take(user, order.takerAssetAddr); BalanceSnapshot.Snapshot memory userMakerAsset = BalanceSnapshot.take(user, order.makerAssetAddr); BalanceSnapshot.Snapshot memory feeCollectorMakerAsset = BalanceSnapshot.take(feeCollector, order.makerAssetAddr); @@ -289,8 +402,19 @@ contract TestAMMWrapperWithPathTradeUniswapV3 is TestAMMWrapperWithPath { uint256 feeFactor = 0; AMMLibEIP712.Order memory order = DEFAULT_ORDER; bytes memory makerSpecificData = _encodeUniswapMultiPoolData(MULTI_POOL_SWAP_TYPE, DEFAULT_MULTI_HOP_PATH, DEFAULT_MULTI_HOP_POOL_FEES); - bytes memory payload = _genTradePayload(order, feeFactor, _signTrade(userPrivateKey, order), makerSpecificData, DEFAULT_MULTI_HOP_PATH); - + bytes memory payload; // Bypass stack too deep error + { + SpenderLibEIP712.SpendWithPermit memory takerAssetPermit = _createSpenderPermitFromOrder(order); + bytes memory takerAssetPermitSig = signSpendWithPermit( + userPrivateKey, + takerAssetPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + payload = _genTradePayload(order, feeFactor, _signTrade(userPrivateKey, order), takerAssetPermitSig, makerSpecificData, DEFAULT_MULTI_HOP_PATH); + } + // More stack than the one curly bracket can handle, + // use the second curly bracket to bypass stack too deep error { uint256 expectedOutAmount = ammQuoter.getMakerOutAmountWithPath( order.makerAddr, diff --git a/contracts/test/forkMainnet/L2Deposit/Deposit.t.sol b/contracts/test/forkMainnet/L2Deposit/Deposit.t.sol index 20866a74..5a8bc6ed 100644 --- a/contracts/test/forkMainnet/L2Deposit/Deposit.t.sol +++ b/contracts/test/forkMainnet/L2Deposit/Deposit.t.sol @@ -5,12 +5,13 @@ pragma abicoder v2; import "contracts/interfaces/IL2Deposit.sol"; import "contracts-test/forkMainnet/L2Deposit/Setup.t.sol"; import "contracts-test/utils/BalanceSnapshot.sol"; +import "contracts-test/utils/Permit.sol"; interface IArbitrumBridge { function delayedMessageCount() external view returns (uint256); } -contract TestL2DepositTopUp is TestL2Deposit { +contract TestL2DepositTopUp is TestL2Deposit, Permit { using BalanceSnapshot for BalanceSnapshot.Snapshot; uint256 arbMaxSubmissionCost = 1e18; @@ -20,9 +21,13 @@ contract TestL2DepositTopUp is TestL2Deposit { function testCannotDepositIfExpired() public { // overwrite expiry DEFAULT_DEPOSIT.expiry = block.timestamp; - // sig is not relevent in this case - bytes memory sig = bytes(""); - bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig)); + // the signatures is not relevent in this case + bytes memory depositActionSig = bytes(""); + bytes memory spenderPermitSig = bytes(""); + bytes memory payload = abi.encodeWithSelector( + L2Deposit.deposit.selector, + IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig) + ); vm.expectRevert("L2Deposit: Deposit is expired"); userProxy.toL2Deposit(payload); @@ -30,8 +35,14 @@ contract TestL2DepositTopUp is TestL2Deposit { function testCannotDepositWithInvalidSig() public { // compose payload with signature from bob - bytes memory sig = _signDeposit(bobPrivateKey, DEFAULT_DEPOSIT); - bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig)); + bytes memory depositActionSig = _signDeposit(bobPrivateKey, DEFAULT_DEPOSIT); + // spenderPermitSig is not relevent in this case + bytes memory spenderPermitSig = bytes(""); + + bytes memory payload = abi.encodeWithSelector( + L2Deposit.deposit.selector, + IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig) + ); vm.expectRevert("L2Deposit: Invalid deposit signature"); userProxy.toL2Deposit(payload); @@ -43,9 +54,22 @@ contract TestL2DepositTopUp is TestL2Deposit { // overwrite deposit data with encoded arbitrum specific params DEFAULT_DEPOSIT.data = abi.encode(arbMaxSubmissionCost, arbMaxGas, arbGasPriceBid); - // compose payload with signature - bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); - bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig)); + // sign the deposit action + bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); + // create spendWithPermit using the deposit and sign it + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT); + bytes memory spenderPermitSig = signSpendWithPermit( + userPrivateKey, + spendWithPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + + bytes memory payload = abi.encodeWithSelector( + L2Deposit.deposit.selector, + IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig) + ); + uint256 callValue = arbMaxSubmissionCost + (arbMaxGas * arbGasPriceBid); vm.expectEmit(true, true, true, true); @@ -69,9 +93,19 @@ contract TestL2DepositTopUp is TestL2Deposit { // overwrite deposit data with encoded arbitrum specific params DEFAULT_DEPOSIT.data = abi.encode(arbMaxSubmissionCost, arbMaxGas, arbGasPriceBid); - // compose payload with signature - bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); - bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig)); + bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT); + bytes memory spenderPermitSig = signSpendWithPermit( + userPrivateKey, + spendWithPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + + bytes memory payload = abi.encodeWithSelector( + L2Deposit.deposit.selector, + IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig) + ); uint256 callValue = arbMaxSubmissionCost + (arbMaxGas * arbGasPriceBid); userProxy.toL2Deposit{ value: callValue }(payload); @@ -88,9 +122,19 @@ contract TestL2DepositTopUp is TestL2Deposit { // overwrite deposit data with encoded arbitrum specific params DEFAULT_DEPOSIT.data = abi.encode(arbMaxSubmissionCost, arbMaxGas, arbGasPriceBid); - // compose payload with signature - bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); - bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig)); + bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT); + bytes memory spenderPermitSig = signSpendWithPermit( + userPrivateKey, + spendWithPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + + bytes memory payload = abi.encodeWithSelector( + L2Deposit.deposit.selector, + IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig) + ); uint256 callValue = arbMaxSubmissionCost + (arbMaxGas * arbGasPriceBid); vm.expectRevert("L2Deposit: Incorrect L2 token address"); @@ -101,9 +145,19 @@ contract TestL2DepositTopUp is TestL2Deposit { // overwrite deposit data with encoded arbitrum specific params DEFAULT_DEPOSIT.data = abi.encode(arbMaxSubmissionCost, arbMaxGas, arbGasPriceBid); - // compose payload with signature - bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); - bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig)); + bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT); + bytes memory spenderPermitSig = signSpendWithPermit( + userPrivateKey, + spendWithPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + + bytes memory payload = abi.encodeWithSelector( + L2Deposit.deposit.selector, + IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig) + ); // compose inbox revert data with defined error uint256 l2Callvalue = 0; // l2 call value 0 by default diff --git a/contracts/test/forkMainnet/L2Deposit/Optimism.t.sol b/contracts/test/forkMainnet/L2Deposit/Optimism.t.sol index 4acbff57..8acf5e26 100644 --- a/contracts/test/forkMainnet/L2Deposit/Optimism.t.sol +++ b/contracts/test/forkMainnet/L2Deposit/Optimism.t.sol @@ -6,8 +6,9 @@ import "contracts/interfaces/IL2Deposit.sol"; import "contracts/utils/L2DepositLibEIP712.sol"; import "contracts-test/forkMainnet/L2Deposit/Setup.t.sol"; import "contracts-test/utils/BalanceSnapshot.sol"; +import "contracts-test/utils/Permit.sol"; -contract TestL2DepositOptimism is TestL2Deposit { +contract TestL2DepositOptimism is TestL2Deposit, Permit { using BalanceSnapshot for BalanceSnapshot.Snapshot; uint32 optL2Gas = 1e6; @@ -24,9 +25,21 @@ contract TestL2DepositOptimism is TestL2Deposit { // overwrite deposit data with encoded optimism specific params DEFAULT_DEPOSIT.data = abi.encode(optL2Gas); - // compose payload with signature - bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); - bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig)); + // sign the deposit action + bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT); + // create spendWithPermit using the deposit and sign it + SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT); + bytes memory spenderPermitSig = signSpendWithPermit( + userPrivateKey, + spendWithPermit, + spender.EIP712_DOMAIN_SEPARATOR(), + SignatureValidator.SignatureType.EIP712 + ); + + bytes memory payload = abi.encodeWithSelector( + L2Deposit.deposit.selector, + IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig) + ); vm.expectEmit(true, true, true, true); emit Deposited( diff --git a/contracts/test/forkMainnet/L2Deposit/Setup.t.sol b/contracts/test/forkMainnet/L2Deposit/Setup.t.sol index f994fd9d..f902a51f 100644 --- a/contracts/test/forkMainnet/L2Deposit/Setup.t.sol +++ b/contracts/test/forkMainnet/L2Deposit/Setup.t.sol @@ -12,6 +12,7 @@ import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "contracts/L2Deposit.sol"; import "contracts/utils/SignatureValidator.sol"; import "contracts/utils/L2DepositLibEIP712.sol"; +import "contracts/utils/SpenderLibEIP712.sol"; import "contracts/interfaces/IArbitrumL1GatewayRouter.sol"; import "contracts/interfaces/IOptimismL1StandardBridge.sol"; @@ -53,18 +54,18 @@ contract TestL2Deposit is StrategySharedSetup { // Set user token balance and approve setEOABalanceAndApprove(user, tokens, 100); - DEFAULT_DEPOSIT = L2DepositLibEIP712.Deposit( - L2DepositLibEIP712.L2Identifier.Arbitrum, // l2Identifier - LON_ADDRESS, // l1TokenAddr - address(arbitrumLONAddr), // l2TokenAddr - user, // sender - user, // recipient - user, // arbitrumRefundAddr - 1 ether, // amount - 1234, // salt - DEFAULT_DEADLINE, // expiry - bytes("") // data - ); + DEFAULT_DEPOSIT = L2DepositLibEIP712.Deposit({ + l2Identifier: L2DepositLibEIP712.L2Identifier.Arbitrum, + l1TokenAddr: LON_ADDRESS, + l2TokenAddr: address(arbitrumLONAddr), + sender: user, + recipient: user, + arbitrumRefundAddr: user, + amount: 1 ether, + salt: 1234, + expiry: DEFAULT_DEADLINE, + data: bytes("") + }); // Label addresses for easier debugging vm.label(user, "User"); @@ -72,15 +73,15 @@ contract TestL2Deposit is StrategySharedSetup { } function _deployStrategyAndUpgrade() internal override returns (address) { - l2Deposit = new L2Deposit( - address(this), // This contract would be the owner - address(userProxy), - WETH_ADDRESS, - address(permanentStorage), - address(spender), - arbitrumL1GatewayRouter, - optimismL1StandardBridge - ); + l2Deposit = new L2Deposit({ + _owner: address(this), // This contract would be the owner + _userProxy: address(userProxy), + _weth: WETH_ADDRESS, + _permStorage: address(permanentStorage), + _spender: address(spender), + _arbitrumL1GatewayRouter: arbitrumL1GatewayRouter, + _optimismL1StandardBridge: optimismL1StandardBridge + }); // Hook up L2Deposit userProxy.upgradeL2Deposit(address(l2Deposit), true); @@ -129,4 +130,17 @@ contract TestL2Deposit is StrategySharedSetup { (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, EIP712SignDigest); return abi.encodePacked(r, s, v, bytes32(0), uint8(SignatureValidator.SignatureType.EIP712)); } + + function _createSpenderPermitFromL2Deposit(L2DepositLibEIP712.Deposit memory _deposit) internal view returns (SpenderLibEIP712.SpendWithPermit memory) { + return + SpenderLibEIP712.SpendWithPermit({ + tokenAddr: _deposit.l1TokenAddr, + requester: address(l2Deposit), + user: _deposit.sender, + recipient: address(l2Deposit), + amount: _deposit.amount, + actionHash: getEIP712Hash(l2Deposit.EIP712_DOMAIN_SEPARATOR(), L2DepositLibEIP712._getDepositHash(_deposit)), + expiry: uint64(_deposit.expiry) + }); + } } diff --git a/contracts/test/utils/Permit.sol b/contracts/test/utils/Permit.sol new file mode 100644 index 00000000..9b88affd --- /dev/null +++ b/contracts/test/utils/Permit.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/cryptography/ECDSA.sol"; +import "forge-std/Test.sol"; +import "contracts/Spender.sol"; +import "contracts/utils/SpenderLibEIP712.sol"; +import "contracts/utils/SignatureValidator.sol"; +import { getEIP712Hash } from "contracts-test/utils/Sig.sol"; + +contract Permit is Test { + function signSpendWithPermit( + uint256 privateKey, + SpenderLibEIP712.SpendWithPermit memory spendWithPermit, + bytes32 domainSeparator, + SignatureValidator.SignatureType sigType + ) internal returns (bytes memory sig) { + uint256 SPEND_WITH_PERMIT_TYPEHASH = 0x52718c957261b99fd72e63478d85d1267cdc812e8249f5a2623566c1818e1ed0; + bytes32 structHash = keccak256( + abi.encode( + SPEND_WITH_PERMIT_TYPEHASH, + spendWithPermit.tokenAddr, + spendWithPermit.requester, + spendWithPermit.user, + spendWithPermit.recipient, + spendWithPermit.amount, + spendWithPermit.actionHash, + spendWithPermit.expiry + ) + ); + bytes32 spendWithPermitHash = getEIP712Hash(domainSeparator, structHash); + if (sigType == SignatureValidator.SignatureType.EIP712 || sigType == SignatureValidator.SignatureType.WalletBytes32) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, spendWithPermitHash); + sig = abi.encodePacked(r, s, v, uint8(sigType)); // new signature format + } else if (sigType == SignatureValidator.SignatureType.Wallet) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, ECDSA.toEthSignedMessageHash(spendWithPermitHash)); + sig = abi.encodePacked(r, s, v, uint8(sigType)); // new signature format + } else { + revert("Invalid signature type"); + } + } +} diff --git a/contracts/utils/SpenderLibEIP712.sol b/contracts/utils/SpenderLibEIP712.sol new file mode 100644 index 00000000..edaac8b0 --- /dev/null +++ b/contracts/utils/SpenderLibEIP712.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.6; + +library SpenderLibEIP712 { + struct SpendWithPermit { + address tokenAddr; + address requester; + address user; + address recipient; + uint256 amount; + bytes32 actionHash; + uint64 expiry; + } + /* + keccak256( + abi.encodePacked( + "SpendWithPermit(", + "address tokenAddr,", + "address requester,", + "address user,", + "address recipient,", + "uint256 amount,", + "bytes32 actionHash,", + "uint64 expiry", + ")" + ) + ); + */ + uint256 public constant SPEND_WITH_PERMIT_TYPEHASH = 0x52718c957261b99fd72e63478d85d1267cdc812e8249f5a2623566c1818e1ed0; + + function _getSpendWithPermitHash(SpendWithPermit memory _spendWithPermit) internal pure returns (bytes32) { + return + keccak256( + abi.encode( + SPEND_WITH_PERMIT_TYPEHASH, + _spendWithPermit.tokenAddr, + _spendWithPermit.requester, + _spendWithPermit.user, + _spendWithPermit.recipient, + _spendWithPermit.amount, + _spendWithPermit.actionHash, + _spendWithPermit.expiry + ) + ); + } +}