Skip to content

Checkout plugin and proxy #598

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@
[submodule "lib/dynamic-contracts"]
path = lib/dynamic-contracts
url = https://github.com/thirdweb-dev/dynamic-contracts
[submodule "lib/prb-proxy"]
path = lib/prb-proxy
url = https://github.com/PaulRBerg/prb-proxy
25 changes: 25 additions & 0 deletions contracts/prebuilts/unaudited/checkout/PluginCheckout.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/

import { IPRBProxyPlugin } from "@prb/proxy/src/interfaces/IPRBProxyPlugin.sol";

import "./TargetCheckout.sol";

contract PluginCheckout is IPRBProxyPlugin, TargetCheckout {
function getMethods() external pure override returns (bytes4[] memory) {
bytes4[] memory methods = new bytes4[](3);
methods[0] = this.withdraw.selector;
methods[1] = this.execute.selector;
methods[2] = this.swapAndExecute.selector;
return methods;
}
}
107 changes: 107 additions & 0 deletions contracts/prebuilts/unaudited/checkout/TargetCheckout.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

import "../../../lib/CurrencyTransferLib.sol";
import "../../../eip/interface/IERC20.sol";

import { IPRBProxy } from "@prb/proxy/src/interfaces/IPRBProxy.sol";
import "./interface/IPluginCheckout.sol";

// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/

contract TargetCheckout is IPluginCheckout {
mapping(address => bool) public isApprovedRouter;

function withdraw(address _token, uint256 _amount) external {
require(msg.sender == IPRBProxy(address(this)).owner(), "Not authorized");

CurrencyTransferLib.transferCurrency(_token, address(this), msg.sender, _amount);
}

function approveSwapRouter(address _swapRouter, bool _toApprove) external {
require(msg.sender == IPRBProxy(address(this)).owner(), "Not authorized");
require(_swapRouter != address(0), "Zero address");

isApprovedRouter[_swapRouter] = _toApprove;
}

function execute(UserOp calldata op) external {
require(_canExecute(op, msg.sender), "Not authorized");

_execute(op);
}

function swapAndExecute(UserOp calldata op, SwapOp calldata swapOp) external {
require(isApprovedRouter[swapOp.router], "Invalid router address");
require(_canExecute(op, msg.sender), "Not authorized");

_swap(swapOp);
_execute(op);
}

// =================================================
// =============== Internal functions ==============
// =================================================

function _execute(UserOp calldata op) internal {
bool success;
if (op.currency == CurrencyTransferLib.NATIVE_TOKEN) {
(success, ) = op.target.call{ value: op.valueToSend }(op.data);
} else {
if (op.valueToSend != 0 && op.approvalRequired) {
IERC20(op.currency).approve(op.target, op.valueToSend);
}

(success, ) = op.target.call(op.data);
}

require(success, "Execution failed");
}

function _swap(SwapOp memory _swapOp) internal {
address _tokenIn = _swapOp.tokenIn;
address _router = _swapOp.router;

// get quote for amountIn
(, bytes memory quoteData) = _router.staticcall(_swapOp.quoteCalldata);
uint256 amountIn;
uint256 offset = _swapOp.amountInOffset;

assembly {
amountIn := mload(add(add(quoteData, 32), offset))
}

// perform swap
bool success;
if (_tokenIn == CurrencyTransferLib.NATIVE_TOKEN) {
(success, ) = _router.call{ value: amountIn }(_swapOp.swapCalldata);
} else {
IERC20(_tokenIn).approve(_swapOp.router, amountIn);
(success, ) = _router.call(_swapOp.swapCalldata);
}

require(success, "Swap failed");
}

function _canExecute(UserOp calldata op, address caller) internal view returns (bool) {
address owner = IPRBProxy(address(this)).owner();
if (owner != caller) {
bool permission = IPRBProxy(address(this)).registry().getPermissionByOwner({
owner: owner,
envoy: caller,
target: op.target
});

return permission;
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

interface IPluginCheckout {
/**
* @notice Details of the transaction to execute on target contract.
*
* @param target Address to send the transaction to
*
* @param currency Represents both native token and erc20 token
*
* @param approvalRequired If need to approve erc20 to the target contract
*
* @param valueToSend Transaction value to send - both native and erc20
*
* @param data Transaction calldata
*/
struct UserOp {
address target;
address currency;
bool approvalRequired;
uint256 valueToSend;
bytes data;
}

struct SwapOp {
address router;
address tokenOut;
address tokenIn;
uint256 amountIn;
uint256 amountInOffset;
bytes swapCalldata;
bytes quoteCalldata;
}

function execute(UserOp calldata op) external;

function swapAndExecute(UserOp calldata op, SwapOp memory swapOp) external;
}
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ remappings = [
'erc721a/=lib/ERC721A/',
'@thirdweb-dev/dynamic-contracts/=lib/dynamic-contracts/',
'lib/sstore2=lib/dynamic-contracts/lib/sstore2/',
'@prb/proxy/=lib/prb-proxy/',
]
fs_permissions = [{ access = "read-write", path = "./src/test/smart-wallet/utils"}]
src = 'contracts'
Expand Down
1 change: 1 addition & 0 deletions lib/prb-proxy
Submodule prb-proxy added at 1c43be
Loading