Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
76 changes: 76 additions & 0 deletions contracts/prebuilts/unaudited/checkout/Checkout.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

import "@openzeppelin/contracts/proxy/Clones.sol";

import "./ICheckout.sol";
import "./Vault.sol";
import "./Executor.sol";

import "../../../external-deps/openzeppelin/utils/Create2.sol";

import "../../../extension/PermissionsEnumerable.sol";

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

contract Checkout is PermissionsEnumerable, ICheckout {
/// @dev Registry of vaults created through this Checkout
mapping(address => bool) isVaultRegistered;

/// @dev Registry of executors created through this Checkout
mapping(address => bool) isExecutorRegistered;

address public immutable vaultImplementation;
address public immutable executorImplementation;

constructor(
address _defaultAdmin,
address _vaultImplementation,
address _executorImplementation
) {
vaultImplementation = _vaultImplementation;
executorImplementation = _executorImplementation;

_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
}

function createVault(address _vaultAdmin, bytes32 _salt) external payable returns (address) {
bytes32 salthash = keccak256(abi.encodePacked(msg.sender, _salt));
address vault = Clones.cloneDeterministic(vaultImplementation, salthash);

(bool success, ) = vault.call(abi.encodeWithSelector(Vault.initialize.selector, _vaultAdmin));

require(success, "Deployment failed");

isVaultRegistered[vault] = true;

return vault;
}

function createExecutor(address _executorAdmin, bytes32 _salt) external payable returns (address) {
bytes32 salthash = keccak256(abi.encodePacked(msg.sender, _salt));
address executor = Clones.cloneDeterministic(executorImplementation, salthash);

(bool success, ) = executor.call(abi.encodeWithSelector(Executor.initialize.selector, _executorAdmin));

require(success, "Deployment failed");

isExecutorRegistered[executor] = true;

return executor;
}

function authorizeVaultToExecutor(address _vault, address _executor) external {
require(IVault(_vault).canAuthorizeVaultToExecutor(msg.sender), "Not authorized");
require(isExecutorRegistered[_executor], "Executor not found");

IVault(_vault).setExecutor(_executor);
}
}
57 changes: 57 additions & 0 deletions contracts/prebuilts/unaudited/checkout/Executor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

import "./IExecutor.sol";
import "./IVault.sol";

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

import "../../../extension/PermissionsEnumerable.sol";
import "../../../extension/Initializable.sol";

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

contract Executor is Initializable, PermissionsEnumerable, IExecutor {
constructor() {
_disableInitializers();
}

function initialize(address _defaultAdmin) external initializer {
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
}

receive() external payable {}

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

if (op.valueToSend != 0) {
IVault(op.vault).transferTokensToExecutor(op.currency, op.valueToSend);
}

bool success;
if (op.currency == CurrencyTransferLib.NATIVE_TOKEN) {
(success, ) = op.target.call{ value: op.valueToSend }(op.data);
} else {
if (op.approvalRequired) {
IERC20(op.currency).approve(op.target, op.valueToSend);
}

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

require(success, "Execution failed");
}
Comment on lines +37 to +56

Check warning

Code scanning / Slither

Unused return

Executor.execute(IExecutor.UserOp) (contracts/prebuilts/unaudited/checkout/Executor.sol#37-56) ignores return value by IERC20(op.currency).approve(op.target,op.valueToSend) (contracts/prebuilts/unaudited/checkout/Executor.sol#49)
Comment on lines +37 to +56

Check warning

Code scanning / Slither

Low-level calls

Low level call in Executor.execute(IExecutor.UserOp) (contracts/prebuilts/unaudited/checkout/Executor.sol#37-56): - (success,None) = op.target.call{value: op.valueToSend}(op.data) (contracts/prebuilts/unaudited/checkout/Executor.sol#46) - (success,None) = op.target.call(op.data) (contracts/prebuilts/unaudited/checkout/Executor.sol#52)

function _canExecute() internal view returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
}
8 changes: 8 additions & 0 deletions contracts/prebuilts/unaudited/checkout/ICheckout.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

interface ICheckout {
function createVault(address _vaultAdmin, bytes32 _salt) external payable returns (address);

function createExecutor(address _executorAdmin, bytes32 _salt) external payable returns (address);
}
30 changes: 30 additions & 0 deletions contracts/prebuilts/unaudited/checkout/IExecutor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

interface IExecutor {
/**
* @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 vault Vault providing liquidity for this transaction
*
* @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;
address vault;
bool approvalRequired;
uint256 valueToSend;
bytes data;
}

function execute(UserOp calldata op) external;
}
19 changes: 19 additions & 0 deletions contracts/prebuilts/unaudited/checkout/IVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

interface IVault {
/// @dev Emitted when contract admin withdraws tokens.
event TokensWithdrawn(address _token, uint256 _amount);

/// @dev Emitted when contract admin deposits tokens.
event TokensDeposited(address _token, uint256 _amount);

/// @dev Emitted when executor contract withdraws tokens.
event TokensTransferredToExecutor(address indexed _executor, address _token, uint256 _amount);

function transferTokensToExecutor(address _token, uint256 _amount) external;

function setExecutor(address _executor) external;

function canAuthorizeVaultToExecutor(address _expectedAdmin) external view returns (bool);
}
103 changes: 103 additions & 0 deletions contracts/prebuilts/unaudited/checkout/Vault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

import "./IVault.sol";

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

import "../../../extension/PermissionsEnumerable.sol";
import "../../../extension/Initializable.sol";

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

contract Vault is Initializable, PermissionsEnumerable, IVault {
/// @dev Mapping from token address to total balance in the vault.
mapping(address => uint256) public tokenBalance;

/// @dev Address of the executor for this vault.
address public executor;

/// @dev Address of the Checkout entrypoint.
address public checkout;

constructor() {
_disableInitializers();
}

function initialize(address _defaultAdmin) external initializer {
checkout = msg.sender;
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
}

function deposit(address _token, uint256 _amount) external payable {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not authorized");

uint256 _actualAmount;

if (_token == CurrencyTransferLib.NATIVE_TOKEN) {
require(msg.value == _amount, "!Amount");
_actualAmount = _amount;

tokenBalance[_token] += _actualAmount;
} else {
uint256 balanceBefore = IERC20(_token).balanceOf(address(this));
CurrencyTransferLib.safeTransferERC20(_token, msg.sender, address(this), _amount);
_actualAmount = IERC20(_token).balanceOf(address(this)) - balanceBefore;

tokenBalance[_token] += _actualAmount;
}

emit TokensDeposited(_token, _actualAmount);
}

function withdraw(address _token, uint256 _amount) external {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not authorized");

uint256 balance = tokenBalance[_token];
// to prevent locking of direct-transferred tokens
tokenBalance[_token] = _amount > balance ? 0 : balance - _amount;

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

emit TokensWithdrawn(_token, _amount);
}

function transferTokensToExecutor(address _token, uint256 _amount) external {
require(_canTransferTokens(), "Not authorized");

uint256 balance = tokenBalance[_token];
require(balance >= _amount, "Not enough balance");

tokenBalance[_token] -= _amount;

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

emit TokensTransferredToExecutor(msg.sender, _token, _amount);
}

function setExecutor(address _executor) external {
require(_canSetExecutor(), "Not authorized");

executor = _executor;
}

function canAuthorizeVaultToExecutor(address _expectedAdmin) external view returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _expectedAdmin);
}

function _canSetExecutor() internal view returns (bool) {
return msg.sender == checkout;
}

function _canTransferTokens() internal view returns (bool) {
return msg.sender == executor;
}
}