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

Checkout plugin and proxy #598

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");

Check warning on line 30 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L29-L30

Added lines #L29 - L30 were not covered by tests

isApprovedRouter[_swapRouter] = _toApprove;

Check warning on line 32 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L32

Added line #L32 was not covered by tests
}

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");

Check warning on line 43 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L42-L43

Added lines #L42 - L43 were not covered by tests

_swap(swapOp);
_execute(op);

Check warning on line 46 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L45-L46

Added lines #L45 - L46 were not covered by tests
}

// =================================================
// =============== 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);

Check warning on line 56 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L56

Added line #L56 was not covered by tests
} 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");
}
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed

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

Check warning on line 70 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L69-L70

Added lines #L69 - L70 were not covered by tests

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

Check warning on line 75 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L73-L75

Added lines #L73 - L75 were not covered by tests

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

Check warning on line 78 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L78

Added line #L78 was not covered by tests
}

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

Check warning on line 84 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L82-L84

Added lines #L82 - L84 were not covered by tests
} else {
IERC20(_tokenIn).approve(_swapOp.router, amountIn);
(success, ) = _router.call(_swapOp.swapCalldata);

Check warning on line 87 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L86-L87

Added lines #L86 - L87 were not covered by tests
}

require(success, "Swap failed");

Check warning on line 90 in contracts/prebuilts/unaudited/checkout/TargetCheckout.sol

View check run for this annotation

Codecov / codecov/patch

contracts/prebuilts/unaudited/checkout/TargetCheckout.sol#L90

Added line #L90 was not covered by tests
}
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed

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
Loading