Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Spender permit feature #110

Open
wants to merge 60 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d1d0156
feat: spender contract and foundry test
oneleo Oct 19, 2022
93833a5
Merge branch 'master' into LABS-960/rfq-with-spender-permit
oneleo Oct 19, 2022
ecbe62e
bug: change to new signature format
oneleo Oct 24, 2022
b05f0ec
feat: replace rfq spendfromuser func with spendfromusertowithpermit
oneleo Oct 24, 2022
a05cfcd
refac: leverage struct spendwithpermit in params
oneleo Oct 26, 2022
eae8717
chg: modify variable names for readability
oneleo Oct 26, 2022
d89c922
refac: move require condition from rfq to spender contract
oneleo Oct 26, 2022
89c376a
refac: modify variables and function in rfq foundry test
oneleo Oct 26, 2022
8af0b60
chg: modify variable names for readability
oneleo Oct 26, 2022
b5b3793
spec: Add requester verification foundry test
oneleo Oct 26, 2022
b2f296b
refac: leverage struct spendwithpermit in params
oneleo Oct 26, 2022
9dca7b1
bug: assign replay protection check before sig valid check
oneleo Oct 26, 2022
c376229
bug: use safeTransfer() in SafeERC20 instead
oneleo Oct 26, 2022
2d21eaa
feat: remove taker/maker spendWithPermit but create they from _order
oneleo Oct 26, 2022
d493f7c
refac: convert the BPS_MAX variable appropriately
oneleo Oct 26, 2022
31ad782
refac: move taker/maker spendWithPermit from fill() to _settle()
oneleo Oct 27, 2022
ac38d3e
feat: modify error message returned when replay protection checks
oneleo Oct 27, 2022
123e7df
refac: use only libconstant.bps_max variable
oneleo Oct 27, 2022
c310fa1
chg: run prettier to format spender.sol to resolve code style issues
oneleo Oct 27, 2022
556ddc7
Merge pull request #99 from consenlabs/LABS-940/spender-adds-backward…
oneleo Oct 27, 2022
65d0d60
Merge branch 'master' into modify-salt-usage-of-spendwithpermit
oneleo Oct 27, 2022
9f242b9
feat: spendWithPermit should contain transaction or order hash
oneleo Oct 28, 2022
45e146f
Merge branch 'master' into spender_permit_feature
oneleo Oct 28, 2022
049cda6
Merge branch 'spender_permit_feature' into modify-salt-usage-of-spend…
oneleo Oct 28, 2022
8acfb68
Merge pull request #105 from consenlabs/modify-salt-usage-of-spendwit…
oneleo Oct 31, 2022
c8b8f2d
Merge branch 'master' into spender_permit_feature
oneleo Nov 4, 2022
2a5da3d
feat: spendWithPermit should contain transaction or order hash
oneleo Nov 4, 2022
36d2730
Merge pull request #111 from consenlabs/modify-txhash-name-of-spendwi…
charlesjhongc Nov 7, 2022
db4726e
refac: rfq, spender contracts changed to non named parameter type
oneleo Nov 7, 2022
db1564a
Merge branch 'master' into spender_permit_feature
oneleo Nov 7, 2022
03d5b72
Merge pull request #112 from consenlabs/rfq_spender_to_no_named_param…
oneleo Nov 8, 2022
77fb55a
refac: move the signSpendWithPermit function in foundry test
oneleo Nov 9, 2022
5627a14
perf: use eip712 domain separator instead of spender contract
oneleo Nov 9, 2022
b9f4bac
bug: revert when sigType is invalid
oneleo Nov 9, 2022
9a1d452
modify spendFromUser --> spendFromUserToWithPermit
Nov 10, 2022
a130849
add helper function regarding spenderPermit on l2Deposit action
Nov 10, 2022
f17e7ab
modify test case using spenderPermit
Nov 10, 2022
0714df3
replace comment for _createSpenderPermitFromL2Deposit
Nov 10, 2022
c3377c7
remove accidentally added files
Nov 10, 2022
f02e23a
remove redundant comments
Nov 10, 2022
4dd27c9
fix nit
Nov 11, 2022
7b4392e
modify solidity objects using named parameters
Nov 11, 2022
be48682
modify DEFAULT_DEPOSIT to _deposit in _createSpenderPermitFromL2Deposit
Nov 11, 2022
b66e418
Merge pull request #114 from consenlabs/move_signspendwithpermit_func
oneleo Nov 11, 2022
6bdf206
feat: replace amm spendfromuser func with spendfromusertowithpermit
oneleo Nov 11, 2022
a52be1c
chg: modify variable name to match internal variable format
oneleo Nov 11, 2022
7150101
Merge branch 'spender_permit_feature' into modify-l2deposit-using-spe…
Nov 14, 2022
a0e582e
remove signSpendWithPermit() from Setup.t.sol
Nov 14, 2022
4130802
use signSpendWithPermit() from contracts-test/utils/Permit.sol
Nov 14, 2022
58d1b78
refec: modify comments and function names
oneleo Nov 14, 2022
4aa11d0
Merge pull request #115 from consenlabs/modify-l2deposit-using-spende…
108356037 Nov 14, 2022
eb9ca88
refec: modify comments and remove unused imported package
oneleo Nov 14, 2022
66b606f
doc: adject _transferTakerAssetToAMM func comment to be clearer
oneleo Nov 15, 2022
2619aca
doc: use named parameters to make code clearer
oneleo Nov 15, 2022
6c542db
doc: apply named parameters to transferTakerAssetToAMM in ammwrapper
oneleo Nov 15, 2022
424feec
Merge pull request #116 from consenlabs/amm-with-spender-permit
oneleo Nov 15, 2022
a67eba2
refac: handle the conflict between RFQ.t.sol and the master branch
oneleo Nov 16, 2022
e8b9284
Merge branch 'master' into spender_permit_feature
oneleo Nov 16, 2022
8dcef47
feat: remove spendFromUser() func in Spender.sol
oneleo Nov 16, 2022
d1e7f20
Merge pull request #119 from consenlabs/remove_spendfromuser_function
charlesjhongc Feb 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 48 additions & 10 deletions contracts/RFQ.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";

Expand All @@ -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";
Expand Down Expand Up @@ -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");
Expand All @@ -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(
Expand All @@ -140,15 +146,45 @@ 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
require(msg.value == _order.takerAssetAmount, "RFQ: insufficient ETH");
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
Expand All @@ -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);
Expand Down
28 changes: 27 additions & 1 deletion contracts/Spender.sol
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;

/************************************************************
Expand Down Expand Up @@ -190,6 +195,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,
Expand Down
18 changes: 9 additions & 9 deletions contracts/SpenderSimulation.sol
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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");
Expand Down
9 changes: 6 additions & 3 deletions contracts/interfaces/IRFQ.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma abicoder v2;

import "./IStrategyBase.sol";
import "../utils/RFQLibEIP712.sol";
import "../utils/SpenderLibEIP712.sol";

/// @title IRFQ Interface
/// @author imToken Labs
Expand All @@ -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);
}
5 changes: 5 additions & 0 deletions contracts/interfaces/ISpender.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0;
pragma abicoder v2;

import "../utils/SpenderLibEIP712.sol";

interface ISpender {
// System events
Expand All @@ -24,4 +27,6 @@ interface ISpender {
address _receiverAddr,
uint256 _amount
) external;

function spendFromUserToWithPermit(SpenderLibEIP712.SpendWithPermit calldata _params, bytes calldata _spendWithPermitSig) external;
}
Loading