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: Add forwarder with permit #1

Merged
merged 4 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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