Skip to content

Commit

Permalink
feat: Add forwarder with permit (#1)
Browse files Browse the repository at this point in the history
* build: compile

* chore: cleanup

* build: add sponsored one

* fix: approve
  • Loading branch information
antoncoding authored Jan 7, 2024
1 parent f827474 commit 0b9e3d9
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 32 deletions.
5 changes: 3 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ optimizer_runs = 2000

[rpc_endpoints]
mainnet = "https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}"
op_goerli = "https://optimism-goerli.blockpi.network/v1/rpc/public"
lyra = "https://rpc.lyra.finance"
op_goerli = "https://optimism-goerli.infura.io/v3/ea21b2313cdf43c28f25b849d7a75274"
lyra = "https://rpc.lyra.finance"
lyra_testnet = "https://l2-prod-testnet-0eakp60405.t.conduit.xyz"
24 changes: 10 additions & 14 deletions script/deploy-forwarder.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,17 @@ contract Deploy is Script {
DeploymentConfig memory config = _getConfig();

// deploy LyraSponsoredForwarder
LyraSponsoredForwarder sponsoredForwarder = new LyraSponsoredForwarder{value: config.fundingAmount}(
config.usdcLocal,
config.socketVault
);
LyraSponsoredForwarder sponsoredForwarder =
new LyraSponsoredForwarder{value: config.fundingAmount}(config.usdcLocal, config.socketVault);

LyraSelfPayingForwarder selfPayingForwarder = new LyraSelfPayingForwarder{value: config.fundingAmount}(
config.usdcLocal,
config.socketVault
);
// LyraSelfPayingForwarder selfPayingForwarder = new LyraSelfPayingForwarder{value: config.fundingAmount}(
// config.usdcLocal,
// config.socketVault
// );

console2.log("LyraSponsoredForwarder deployed at: ", address(sponsoredForwarder));

console2.log("LyraSelfPayingForwarder deployed at: ", address(selfPayingForwarder));
// console2.log("LyraSelfPayingForwarder deployed at: ", address(selfPayingForwarder));

vm.stopBroadcast();
}
Expand All @@ -46,23 +44,21 @@ contract Deploy is Script {
if (block.chainid == 420) {
// OP-Goerli
return DeploymentConfig({
fundingAmount: 0.15 ether,
fundingAmount: 0.07 ether,
usdcLocal: 0x0f8BEaf58d4A237C88c9ed99D82003ab5c252c26, // our clone of USDC on op-goerli
// Socket configs
// See: https://github.com/SocketDotTech/app-chain-token/blob/lyra-tesnet-to-prod/deployments/prod_lyra_addresses.json
socketVault: 0x3d74c019E9caCBc968cF31B0810044a030B3E903
})
});
// socketConnector: 0xfBf496B6DBda9d5e778e2563493BCb32F5A52B51
;
} else if (block.chainid == 1) {
// Mainnet
return DeploymentConfig({
fundingAmount: 0.15 ether,
usdcLocal: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // mainnet USDC
socketVault: 0x6D303CEE7959f814042D31E0624fB88Ec6fbcC1d
})
});
// socketConnector: 0x0000000000000000000000000000000000000001 // todo: add l1 address
;
}

revert("No config for this network! Please set config in script/Deploy.s.sol");
Expand Down
5 changes: 3 additions & 2 deletions script/deploy-paymaster.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ contract DeployPaymaster is Script {
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
VerifyingPaymasterFix paymaster =
new VerifyingPaymasterFix(IEntryPoint(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789), vm.addr(deployerPrivateKey));
VerifyingPaymasterFix paymaster = new VerifyingPaymasterFix(
IEntryPoint(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789), vm.addr(deployerPrivateKey)
);
console2.log("VerifyingPaymasterFix deployed at: ", address(paymaster));
vm.stopBroadcast();
}
Expand Down
104 changes: 104 additions & 0 deletions src/gelato/LyraPermitSelfPayingForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

import {GelatoRelayContextERC2771} from "../../lib/relay-context-contracts/contracts/GelatoRelayContextERC2771.sol";

import {ISocketVault} from "../interfaces/ISocketVault.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {ILightAccountFactory} from "../interfaces/ILightAccountFactory.sol";

/**
* @title LyraPermitSelfPayingForwarder
* @notice Use this contract to allow gasless transactions, users pay gelato relayers in tokens like (USDC.e)
*
* @dev All functions are guarded with onlyGelatoRelayERC2771. They should only be called by GELATO_RELAY_ERC2771 or GELATO_RELAY_CONCURRENT_ERC2771
* @dev Someone need to fund this contract with ETH to use Socket Bridge
*/
contract LyraPermitSelfPayingForwarder is Ownable, GelatoRelayContextERC2771 {
///@dev SocketVault address.
address public immutable socketVault;

///@dev local token address. This token must support permit
address public immutable token;

///@dev Light Account factory address.
/// See this script for more info https://github.com/alchemyplatform/light-account/blob/main/script/Deploy_LightAccountFactory.s.sol
address public constant lightAccountFactory = 0x000000893A26168158fbeaDD9335Be5bC96592E2;

struct PermitData {
uint256 value;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}

constructor(address _token, address _socketVault) payable GelatoRelayContextERC2771() {
token = _token;
socketVault = _socketVault;

IERC20(_token).approve(_socketVault, type(uint256).max);
}

/**
* @notice Deposit USDC to L2 through socket bridge. Gas is paid in token
* @dev Users never have to approve USDC to this contract.
* @param maxFeeToken Maximum fee in that user is willing to pay
* @param isScwWallet True if user wants to deposit to default LightAccount on L2. False if the user wants to deposit to its own L2 address
* @param minGasLimit Minimum gas limit for the L2 execution
* @param connector Socket Connector
* @param permitData Data and signatures for permit
*/
function depositGasless(
uint256 maxFeeToken,
bool isScwWallet,
uint32 minGasLimit,
address connector,
PermitData calldata permitData
) external payable onlyGelatoRelayERC2771 {
address msgSender = _getMsgSender();

// use try catch so that others cannot grief by submitting the same permit data before this tx
try IERC20Permit(token).permit(
msgSender, address(this), permitData.value, permitData.deadline, permitData.v, permitData.r, permitData.s
) {} catch {}

IERC20(token).transferFrom(msgSender, address(this), permitData.value);

// Pay gelato fee, reverts if exceeded max fee
_transferRelayFeeCapped(maxFeeToken);

uint256 remaining = permitData.value - _getFee();

uint256 socketFee = ISocketVault(socketVault).getMinFees(connector, minGasLimit);

// Pay socket fee and deposit to Lyra Chain
ISocketVault(socketVault).depositToAppChain{value: socketFee}(
_getL2Receiver(msgSender, isScwWallet), remaining, minGasLimit, connector
);
}

/**
* @notice Return the receiver address on L2
*/
function _getL2Receiver(address msgSender, bool isScwWallet) internal view returns (address) {
if (isScwWallet) {
return ILightAccountFactory(lightAccountFactory).getAddress(msgSender, 0);
} else {
return msgSender;
}
}

/**
* @dev Owner can withdraw ETH deposited to cover socket protocol fee
*/
function withdrawETH() external onlyOwner {
payable(msg.sender).transfer(address(this).balance);
}

receive() external payable {}
}
103 changes: 103 additions & 0 deletions src/gelato/LyraPermitSponsoredForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import {Ownable, Context} from "@openzeppelin/contracts/access/Ownable.sol";

import {ERC2771Context} from "../../lib/relay-context-contracts/contracts/vendor/ERC2771Context.sol";

import {ISocketVault} from "../interfaces/ISocketVault.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {ILightAccountFactory} from "../interfaces/ILightAccountFactory.sol";

/**
* @title LyraPermitSponsoredForwarder
* @notice Use this contract to allow gasless transactions, we sponsor the gas for users
*
*/
contract LyraPermitSponsoredForwarder is Ownable, ERC2771Context {
///@dev SocketVault address.
address public immutable socketVault;

///@dev local token address. This token must support permit
address public immutable token;

///@dev Light Account factory address.
address public constant lightAccountFactory = 0x000000893A26168158fbeaDD9335Be5bC96592E2;

struct PermitData {
uint256 value;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}

constructor(address _token, address _socketVault)
payable
ERC2771Context(0xd8253782c45a12053594b9deB72d8e8aB2Fca54c)
{
token = _token;
socketVault = _socketVault;

IERC20(_token).approve(_socketVault, type(uint256).max);
}

/**
* @notice Deposit USDC to L2 through socket bridge. Gas is paid in token
* @dev Users never have to approve USDC to this contract.
* @param isScwWallet True if user wants to deposit to default LightAccount on L2. False if the user wants to deposit to its own L2 address
* @param minGasLimit Minimum gas limit for the L2 execution
* @param connector Socket Connector
* @param permitData Data and signatures for permit
*/
function depositGasless(bool isScwWallet, uint32 minGasLimit, address connector, PermitData calldata permitData)
external
payable
{
address msgSender = _msgSender();

// use try catch so that others cannot grief by submitting the same permit data before this tx
try IERC20Permit(token).permit(
msgSender, address(this), permitData.value, permitData.deadline, permitData.v, permitData.r, permitData.s
) {} catch {}

IERC20(token).transferFrom(msgSender, address(this), permitData.value);

uint256 socketFee = ISocketVault(socketVault).getMinFees(connector, minGasLimit);

// Pay socket fee and deposit to Lyra Chain
ISocketVault(socketVault).depositToAppChain{value: socketFee}(
_getL2Receiver(msgSender, isScwWallet), permitData.value, minGasLimit, connector
);
}

/**
* @notice Return the receiver address on L2
*/
function _getL2Receiver(address msgSender, bool isScwWallet) internal view returns (address) {
if (isScwWallet) {
return ILightAccountFactory(lightAccountFactory).getAddress(msgSender, 0);
} else {
return msgSender;
}
}

/**
* @dev Owner can withdraw ETH deposited to cover socket protocol fee
*/
function withdrawETH() external onlyOwner {
payable(msg.sender).transfer(address(this).balance);
}

function _msgSender() internal view override(Context, ERC2771Context) returns (address sender) {
return ERC2771Context._msgSender();
}

function _msgData() internal view override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}

receive() external payable {}
}
14 changes: 0 additions & 14 deletions src/mocks/USDC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ interface IERC20 {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

abstract contract AbstractFiatTokenV1 is IERC20 {
Expand Down Expand Up @@ -385,7 +384,6 @@ contract Ownable {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -474,7 +472,6 @@ contract Pausable is Ownable {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -563,7 +560,6 @@ contract Blacklistable is Ownable {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -1100,7 +1096,6 @@ library SafeERC20 {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

contract Rescuable is Ownable {
Expand Down Expand Up @@ -1170,7 +1165,6 @@ contract Rescuable is Ownable {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -1202,7 +1196,6 @@ contract FiatTokenV1_1 is FiatTokenV1, Rescuable {}
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
Expand Down Expand Up @@ -1235,7 +1228,6 @@ abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -1302,7 +1294,6 @@ library ECRecover {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -1375,7 +1366,6 @@ library EIP712 {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -1411,7 +1401,6 @@ contract EIP712Domain {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -1599,7 +1588,6 @@ abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -1666,7 +1654,6 @@ abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

/**
Expand Down Expand Up @@ -1855,7 +1842,6 @@ contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

pragma solidity ^0.8.0;

// solhint-disable func-name-mixedcase
Expand Down

0 comments on commit 0b9e3d9

Please sign in to comment.