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

feat: permit2: write a permit2 router #420

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/permit2"]
path = lib/permit2
url = https://github.com/Uniswap/permit2
4 changes: 4 additions & 0 deletions addresses/context/goerli.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@
{
"address": "0xd77b79be3e85351ff0cbe78f1b58cf8d1064047c",
"name": "DAI"
},
{
"address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"name": "Permit2"
}
]
4 changes: 4 additions & 0 deletions addresses/context/matic.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@
{
"address": "0x59a424169526ECae25856038598F862043DCeDf7",
"name": "MgvGovernance"
},
{
"address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"name": "Permit2"
}
]
4 changes: 4 additions & 0 deletions addresses/context/maticmum.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
{
"address": "0x47897EE61498D02B18794601Ed3A71896A1Ff894",
"name": "MgvGovernance"
},
{
"address": "0x000000000022D473030F116dDEE9F6B43aC78BA3",
"name": "Permit2"
}
]
1 change: 1 addition & 0 deletions lib/permit2
Submodule permit2 added at 576f54
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;

import {IPermit2} from "lib/permit2/src/interfaces/IPermit2.sol";
import {Script, console} from "forge-std/Script.sol";
import {MangroveOrder, IERC20, IMangrove} from "mgv_src/strategies/MangroveOrder.sol";
import {Deployer} from "mgv_script/lib/Deployer.sol";
Expand All @@ -17,6 +18,7 @@ contract MangroveOrderDeployer is Deployer {
function run() public {
innerRun({
mgv: IMangrove(envAddressOrName("MGV", "Mangrove")),
permit2: IPermit2(envAddressOrName("Permit2", "Permit2")),
admin: envAddressOrName("MGV_GOVERNANCE", broadcaster())
});
outputDeployment();
Expand All @@ -26,7 +28,7 @@ contract MangroveOrderDeployer is Deployer {
* @param mgv The Mangrove that MangroveOrder should operate on
* @param admin address of the admin on MangroveOrder after deployment
*/
function innerRun(IMangrove mgv, address admin) public {
function innerRun(IMangrove mgv, IPermit2 permit2, address admin) public {
MangroveOrder mgvOrder;
// Bug workaround: Foundry has a bug where the nonce is not incremented when MangroveOrder is deployed.
// We therefore ensure that this happens.
Expand All @@ -36,9 +38,9 @@ contract MangroveOrderDeployer is Deployer {
// so setting offer logic's gasreq to 35K is enough
// we use 60K here in order to allow partial fills to repost on top of up to 5 identical offers.
if (forMultisig) {
mgvOrder = new MangroveOrder{salt:salt}(mgv, admin, 60_000);
mgvOrder = new MangroveOrder{salt:salt}(mgv, permit2, admin, 60_000);
} else {
mgvOrder = new MangroveOrder(mgv, admin, 60_000);
mgvOrder = new MangroveOrder(mgv, permit2, admin, 60_000);
}
// Bug workaround: See comment above `nonce` further up
if (nonce == vm.getNonce(broadcaster())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {IPermit2} from "lib/permit2/src/interfaces/IPermit2.sol";
import {MangroveOrder, IERC20, IMangrove} from "mgv_src/strategies/MangroveOrder.sol";

import {Deployer} from "mgv_script/lib/Deployer.sol";
Expand All @@ -19,6 +20,7 @@ contract MumbaiMangroveOrderDeployer is Deployer {
function runWithChainSpecificParams() public {
new MangroveOrderDeployer().innerRun({
mgv: IMangrove(envAddressOrName("MGV", "Mangrove")),
permit2: IPermit2(envAddressOrName("Permit2", "Permit2")),
admin: envAddressOrName("MGV_GOVERNANCE", broadcaster())
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {IPermit2} from "lib/permit2/src/interfaces/IPermit2.sol";
import {MangroveOrder, IERC20, IMangrove} from "mgv_src/strategies/MangroveOrder.sol";

import {Deployer} from "mgv_script/lib/Deployer.sol";
Expand All @@ -20,6 +21,10 @@ contract PolygonMangroveOrderDeployer is Deployer {

function runWithChainSpecificParams() public {
mangroveOrderDeployer = new MangroveOrderDeployer();
mangroveOrderDeployer.innerRun({mgv: IMangrove(fork.get("Mangrove")), admin: fork.get("MgvGovernance")});
mangroveOrderDeployer.innerRun({
mgv: IMangrove(fork.get("Mangrove")),
permit2: IPermit2(envAddressOrName("Permit2", "Permit2")),
admin: fork.get("MgvGovernance")
});
}
}
9 changes: 7 additions & 2 deletions script/toy/MangroveJs.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {IMangrove} from "mgv_src/IMangrove.sol";
import {Deployer} from "mgv_script/lib/Deployer.sol";
import {ActivateMarket} from "mgv_script/core/ActivateMarket.s.sol";
import {PoolAddressProviderMock} from "mgv_script/toy/AaveMock.sol";
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol";
import "forge-std/console.sol";

/*
Expand All @@ -34,8 +36,11 @@ contract MangroveJsDeploy is Deployer {
IERC20 public weth;
SimpleTestMaker public simpleTestMaker;
MangroveOrder public mgo;
IPermit2 public permit2;

function run() public {
DeployPermit2 deployPermit2 = new DeployPermit2();
permit2 = IPermit2(deployPermit2.deployPermit2()); // deploy permit2 using the precompiled bytecode
innerRun({gasprice: 1, gasmax: 2_000_000, gasbot: broadcaster()});
outputDeployment();
}
Expand Down Expand Up @@ -118,7 +123,7 @@ contract MangroveJsDeploy is Deployer {
activateMarket.innerRun(mgv, mgvReader, weth, usdc, 1e9, 1e9 / 1000, 0);

MangroveOrderDeployer mgoeDeployer = new MangroveOrderDeployer();
mgoeDeployer.innerRun({admin: broadcaster(), mgv: IMangrove(payable(mgv))});
mgoeDeployer.innerRun({admin: broadcaster(), mgv: IMangrove(payable(mgv)), permit2: permit2});

address[] memory underlying =
dynamic([address(tokenA), address(tokenB), address(dai), address(usdc), address(weth)]);
Expand All @@ -135,7 +140,7 @@ contract MangroveJsDeploy is Deployer {
});

broadcast();
mgo = new MangroveOrder({mgv: IMangrove(payable(mgv)), deployer: broadcaster(), gasreq:30_000});
mgo = new MangroveOrder({mgv: IMangrove(payable(mgv)), deployer: broadcaster(), gasreq:30_000, permit2: permit2});
fork.set("MangroveOrder", address(mgo));
}
}
72 changes: 68 additions & 4 deletions src/strategies/MangroveOrder.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// SPDX-License-Identifier: BSD-2-Clause
pragma solidity ^0.8.10;

import {IPermit2} from "lib/permit2/src/interfaces/IPermit2.sol";
import {ISignatureTransfer} from "lib/permit2/src/interfaces/ISignatureTransfer.sol";
import {IAllowanceTransfer} from "lib/permit2/src/interfaces/IAllowanceTransfer.sol";
import {IMangrove} from "mgv_src/IMangrove.sol";
import {Forwarder, MangroveOffer} from "mgv_src/strategies/offer_forwarder/abstract/Forwarder.sol";
import {IOrderLogic} from "mgv_src/strategies/interfaces/IOrderLogic.sol";
import {SimpleRouter} from "mgv_src/strategies/routers/SimpleRouter.sol";
import {Permit2Router} from "mgv_src/strategies/routers/Permit2Router.sol";
import {TransferLib} from "mgv_src/strategies/utils/TransferLib.sol";
import {MgvLib, IERC20} from "mgv_src/MgvLib.sol";

Expand All @@ -23,9 +26,12 @@ contract MangroveOrder is Forwarder, IOrderLogic {

///@notice MangroveOrder is a Forwarder logic with a simple router.
///@param mgv The mangrove contract on which this logic will run taker and maker orders.
///@param permit2 The permit2 contract
///@param deployer The address of the admin of `this` at the end of deployment
///@param gasreq The gas required for `this` to execute `makerExecute` and `makerPosthook` when called by mangrove for a resting order.
constructor(IMangrove mgv, address deployer, uint gasreq) Forwarder(mgv, new SimpleRouter(), gasreq) {
constructor(IMangrove mgv, IPermit2 permit2, address deployer, uint gasreq)
Forwarder(mgv, new Permit2Router(permit2), gasreq)
{
// adding `this` contract to authorized makers of the router before setting admin rights of the router to deployer
router().bind(address(this));
router().setAdmin(deployer);
Expand Down Expand Up @@ -119,8 +125,45 @@ contract MangroveOrder is Forwarder, IOrderLogic {
}
}

///@inheritdoc IOrderLogic
function take(TakerOrder calldata tko) external payable returns (TakerOrderResult memory res) {
///@notice pull inbound_tkn from the msg.sender with permit and then the forward market order to MGV
///@param outbound_tkn outbound_tkn
///@param inbound_tkn inbound_tkn
///@param takerWants Amount of outbound_tkn taker wants
///@param takerGives Amount of inbound_tkn taker gives
///@param fillWants isBid
///@param permit The permit data signed over by the owner
///@param signature The signature to verify
///@return totalGot Amount of outbound_tkn received
///@return totalGave Amount of inbound_tkn received
///@return totalPenalty Penalty received
///@return feePaid Fee paid
function marketOrderWithTransferApproval(
IERC20 outbound_tkn,
IERC20 inbound_tkn,
uint takerWants,
uint takerGives,
bool fillWants,
ISignatureTransfer.PermitTransferFrom calldata permit,
bytes calldata signature
) external returns (uint totalGot, uint totalGave, uint totalPenalty, uint feePaid) {
uint pulled = Permit2Router(address(router())).pull(inbound_tkn, msg.sender, takerGives, true, permit, signature);
require(pulled == takerGives, "mgvOrder/transferInFail");
(totalGot, totalGave, totalPenalty, feePaid) =
MGV.marketOrder(address(outbound_tkn), address(inbound_tkn), takerWants, takerGives, fillWants);

uint fund = takerGives - totalGave;
if (fund > 0) {
// refund the sender
(bool noRevert,) =
address(router()).call(abi.encodeWithSelector(router().push.selector, inbound_tkn, msg.sender, fund));
require(noRevert, "mgvOrder/refundInboundTknFail");
}
}

///@notice take implementation
///@param tko TakerOrder struct
///@return res TakerOrderResult Order result
function __take__(TakerOrder calldata tko) internal returns (TakerOrderResult memory res) {
// Checking whether order is expired
require(tko.expiryDate == 0 || block.timestamp <= tko.expiryDate, "mgvOrder/expired");

Expand Down Expand Up @@ -222,6 +265,27 @@ contract MangroveOrder is Forwarder, IOrderLogic {
return res;
}

///@inheritdoc IOrderLogic
///@param tko TakerOrder struct
///@return TakerOrderResult Result of the take call
function take(TakerOrder calldata tko) external payable returns (TakerOrderResult memory) {
return __take__(tko);
}

///@notice call permit2 permit and then call take, this can be used to first approve and then take
///@param tko TakerOrder struct
///@param permit The permit data signed over by the owner
///@param signature The signature to verify
///@return TakerOrderResult Result of the take call
function takeWithPermit(
TakerOrder calldata tko,
IAllowanceTransfer.PermitSingle memory permit,
bytes calldata signature
) external payable returns (TakerOrderResult memory) {
Permit2Router(address(router())).permit2().permit(msg.sender, permit, signature);
return __take__(tko);
}

///@notice logs `OrderSummary`
///@param tko the arguments in memory of the taker order
///@param res the result of the taker order.
Expand Down
3 changes: 2 additions & 1 deletion src/strategies/offer_forwarder/OfferForwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ pragma solidity ^0.8.10;

import {Forwarder, IMangrove, IERC20} from "mgv_src/strategies/offer_forwarder/abstract/Forwarder.sol";
import {ILiquidityProvider} from "mgv_src/strategies/interfaces/ILiquidityProvider.sol";
import {SimpleRouter, AbstractRouter} from "mgv_src/strategies/routers/SimpleRouter.sol";
import {SimpleRouter} from "mgv_src/strategies/routers/SimpleRouter.sol";
import {AbstractRouter} from "mgv_src/strategies/routers/AbstractRouter.sol";
import {MgvLib} from "mgv_src/MgvLib.sol";

contract OfferForwarder is ILiquidityProvider, Forwarder {
Expand Down
1 change: 1 addition & 0 deletions src/strategies/routers/AbstractRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.10;

import {AccessControlled} from "mgv_src/strategies/utils/AccessControlled.sol";
import {IERC20} from "mgv_src/MgvLib.sol";
import {ISignatureTransfer} from "lib/permit2/src/interfaces/ISignatureTransfer.sol";

/// @title AbstractRouter
/// @notice Partial implementation and requirements for liquidity routers.
Expand Down
97 changes: 97 additions & 0 deletions src/strategies/routers/Permit2Router.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: BSD-2-Clause
pragma solidity ^0.8.10;

import {IERC20} from "mgv_src/MgvLib.sol";
import {IPermit2} from "lib/permit2/src/interfaces/IPermit2.sol";
import {TransferLib} from "mgv_src/strategies/utils/TransferLib.sol";
import {ISignatureTransfer} from "lib/permit2/src/interfaces/ISignatureTransfer.sol";
import {SimpleRouterWithoutGasReq} from "./SimpleRouter.sol";

//@title `Permit2Router` instances pull (push) liquidity directly from (to) the an offer owner's account using permit2 contract
//@dev Maker contracts using this router must make sure that the reserve approves the permit2 for all asset that will be pulled (outbound tokens), and then the user needs either approve router inside permit2 or he can use just in time signature to authorize transfer
contract Permit2Router is SimpleRouterWithoutGasReq {
IPermit2 public permit2;

constructor(IPermit2 _permit2) SimpleRouterWithoutGasReq(74_000) {
permit2 = _permit2;
}

/// @notice transfers an amount of tokens from the reserve to the maker.
/// @param token Token to be transferred
/// @param owner The account from which the tokens will be transferred.
/// @param amount The amount of tokens to be transferred
/// @param strict wether the caller maker contract wishes to pull at most `amount` tokens of owner.
/// @return pulled The amount pulled if successful (will be equal to `amount`); otherwise, 0.
/// @dev requires approval from `owner` for `this` to transfer `token`.
function __pull__(IERC20 token, address owner, uint amount, bool strict)
internal
virtual
override
returns (uint pulled)
{
amount = strict ? amount : token.balanceOf(owner);
if (TransferLib.transferTokenFromWithPermit2(permit2, token, owner, msg.sender, amount)) {
return amount;
} else {
return 0;
}
}

///@notice router-dependent implementation of the `pull` function
///@param token Token to be transferred
///@param owner determines the location of the reserve (router implementation dependent).
///@param amount The amount of tokens to be transferred
///@param strict wether the caller maker contract wishes to pull at most `amount` tokens of owner.
///@param transferDetails The spender's requested transfer details for the permitted token
///@param signature The signature to verify
///@return pulled The amount pulled if successful; otherwise, 0.
function __pull__(
IERC20 token,
address owner,
uint amount,
bool strict,
ISignatureTransfer.PermitTransferFrom calldata transferDetails,
bytes calldata signature
) internal returns (uint pulled) {
amount = strict ? amount : token.balanceOf(owner);
if (
TransferLib.transferTokenFromWithPermit2Signature(permit2, owner, msg.sender, amount, transferDetails, signature)
) {
return amount;
} else {
return 0;
}
}

///@notice pulls liquidity from the reserve and sends it to the calling maker contract.
///@param token is the ERC20 managing the pulled asset
///@param reserveId identifies the fund owner (router implementation dependent).
///@param amount of `token` the maker contract wishes to pull from its reserve
///@param strict when the calling maker contract accepts to receive more funds from reserve than required (this may happen for gas optimization)
///@param permit The permit data signed over by the owner
///@param signature The signature to verify
///@return pulled the amount that was successfully pulled.
function pull(
IERC20 token,
address reserveId,
uint amount,
bool strict,
ISignatureTransfer.PermitTransferFrom calldata permit,
bytes calldata signature
) external onlyBound returns (uint pulled) {
if (strict && amount == 0) {
return 0;
}
pulled = __pull__(token, reserveId, amount, strict, permit, signature);
}

///@notice router-dependent implementation of the `checkList` function
///@notice verifies all required approval involving `this` router (either as a spender or owner)
///@dev `checkList` returns normally if all needed approval are strictly positive. It reverts otherwise with a reason.
///@param token is the asset whose approval must be checked
///@param owner the account that requires asset pulling/pushing
function __checkList__(IERC20 token, address owner) internal view virtual override {
// verifying that `this` router can withdraw tokens from owner (required for `withdrawToken` and `pull`)
require(token.allowance(owner, address(permit2)) > 0, "SimpleRouter/NotApprovedByOwner");
}
}
Loading