Skip to content

Commit ce5feb9

Browse files
committed
init EnsoWalletV2 and EnsoWalletV2Factory
1 parent 4004719 commit ce5feb9

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

src/wallet/EnsoWalletV2.sol

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.20;
3+
4+
import { AbstractEnsoShortcuts } from "../AbstractEnsoShortcuts.sol";
5+
import { AbstractMultiSend } from "../AbstractMultiSend.sol";
6+
import { Withdrawable } from "../utils/Withdrawable.sol";
7+
8+
import { Initializable } from "openzeppelin-contracts/proxy/utils/Initializable.sol";
9+
10+
contract EnsoWalletV2 is AbstractMultiSend, AbstractEnsoShortcuts, Initializable, Withdrawable {
11+
string public constant VERSION = "1.0.0";
12+
address public factory;
13+
address private _owner;
14+
15+
error InvalidSender(address sender);
16+
17+
modifier onlyOwner() {
18+
_checkOwner();
19+
_;
20+
}
21+
22+
/**
23+
* @notice Initializes the wallet with an owner address
24+
* @param owner_ The address that will own this wallet
25+
*/
26+
function initialize(address owner_) external initializer {
27+
_owner = owner_;
28+
// sender has to be the factory
29+
factory = msg.sender;
30+
}
31+
32+
/**
33+
* @notice Executes an arbitrary call to a target contract
34+
* @param target The address of the contract to call
35+
* @param value The amount of native token to send with the call
36+
* @param data The calldata to send to the target contract
37+
* @return success Whether the call succeeded
38+
*/
39+
function execute(
40+
address target,
41+
uint256 value,
42+
bytes memory data
43+
)
44+
external
45+
payable
46+
onlyOwner
47+
returns (bool success)
48+
{
49+
assembly {
50+
success := call(gas(), target, value, add(data, 0x20), mload(data), 0, 0)
51+
}
52+
}
53+
54+
/**
55+
* @notice Executes a shortcut
56+
* @dev Can be called by owner or factory
57+
* @param accountId The bytes32 value representing an API user
58+
* @param requestId The bytes32 value representing an API request
59+
* @param commands An array of bytes32 values that encode calls
60+
* @param state An array of bytes that are used to generate call data for each command
61+
* @return response Array of response data from each executed command
62+
*/
63+
function executeShortcut(
64+
bytes32 accountId,
65+
bytes32 requestId,
66+
bytes32[] calldata commands,
67+
bytes[] calldata state
68+
)
69+
public
70+
payable
71+
override
72+
returns (bytes[] memory response)
73+
{
74+
return super.executeShortcut(accountId, requestId, commands, state);
75+
}
76+
77+
/// @notice Abstract override function to return owner
78+
function owner() public view override returns (address) {
79+
return _owner;
80+
}
81+
82+
/// @notice Abstract override function to validate msg.sender
83+
function _checkMsgSender() internal view override(AbstractEnsoShortcuts, AbstractMultiSend) {
84+
if (msg.sender != factory && msg.sender != owner()) revert InvalidSender(msg.sender);
85+
}
86+
87+
/// @notice Abstract override function to validate if sender is the owner
88+
function _checkOwner() internal view override {
89+
if (msg.sender != owner()) revert InvalidSender(msg.sender);
90+
}
91+
}

src/wallet/EnsoWalletV2Factory.sol

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.20;
3+
4+
import { Token, TokenType } from "../interfaces/IEnsoRouter.sol";
5+
6+
import { IEnsoWalletV2 } from "./interfaces/IEnsoWalletV2.sol";
7+
8+
import { IERC1155 } from "openzeppelin-contracts/token/ERC1155/IERC1155.sol";
9+
import { IERC20, SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
10+
import { IERC721 } from "openzeppelin-contracts/token/ERC721/IERC721.sol";
11+
import { LibClone } from "solady/utils/LibClone.sol";
12+
13+
contract EnsoWalletV2Factory {
14+
using LibClone for address;
15+
using SafeERC20 for IERC20;
16+
17+
address public immutable implementation;
18+
19+
event EnsoWalletV2Deployed(address wallet, address indexed account);
20+
21+
error WrongMsgValue(uint256 value, uint256 expectedAmount);
22+
error UnsupportedTokenType(TokenType tokenType);
23+
24+
constructor(address implementation_) {
25+
implementation = implementation_;
26+
}
27+
28+
function deploy(address account) external returns (address) {
29+
return _deploy(account);
30+
}
31+
32+
function deployAndExecute(Token calldata tokenIn, bytes calldata data) external payable returns (address, bool) {
33+
return _deployAndExecute(tokenIn, data);
34+
}
35+
36+
function getAddress(address account) external view returns (address) {
37+
bytes32 salt = _getSalt(account);
38+
return implementation.predictDeterministicAddress(salt, address(this));
39+
}
40+
41+
function _deployAndExecute(
42+
Token calldata tokenIn,
43+
bytes calldata data
44+
)
45+
private
46+
returns (address wallet, bool success)
47+
{
48+
// strictly only msg.sender can deploy and execute
49+
wallet = _deploy(msg.sender);
50+
bool isNativeAsset = _transfer(tokenIn, wallet);
51+
if (!isNativeAsset && msg.value != 0) revert WrongMsgValue(msg.value, 0);
52+
(success,) = wallet.call{ value: msg.value }(data);
53+
}
54+
55+
function _deploy(address account) private returns (address wallet) {
56+
bytes32 salt = _getSalt(account);
57+
wallet = implementation.predictDeterministicAddress(salt, address(this));
58+
if (wallet.code.length == 0) {
59+
implementation.cloneDeterministic(salt);
60+
IEnsoWalletV2(wallet).initialize(account);
61+
emit EnsoWalletV2Deployed(wallet, account);
62+
}
63+
}
64+
65+
function _transfer(Token calldata token, address receiver) private returns (bool isNativeAsset) {
66+
TokenType tokenType = token.tokenType;
67+
68+
if (tokenType == TokenType.ERC20) {
69+
(IERC20 erc20, uint256 amount) = abi.decode(token.data, (IERC20, uint256));
70+
erc20.safeTransferFrom(msg.sender, receiver, amount);
71+
} else if (tokenType == TokenType.Native) {
72+
// no need to get amount, it will come from msg.value
73+
isNativeAsset = true;
74+
} else if (tokenType == TokenType.ERC721) {
75+
(IERC721 erc721, uint256 tokenId) = abi.decode(token.data, (IERC721, uint256));
76+
erc721.safeTransferFrom(msg.sender, receiver, tokenId);
77+
} else if (tokenType == TokenType.ERC1155) {
78+
(IERC1155 erc1155, uint256 tokenId, uint256 amount) = abi.decode(token.data, (IERC1155, uint256, uint256));
79+
erc1155.safeTransferFrom(msg.sender, receiver, tokenId, amount, "0x");
80+
} else {
81+
revert UnsupportedTokenType(tokenType);
82+
}
83+
}
84+
85+
function _getSalt(address account) internal pure returns (bytes32) {
86+
return keccak256(abi.encode(account));
87+
}
88+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.20;
3+
4+
interface IEnsoWalletV2 {
5+
error InvalidSender(address sender);
6+
7+
function initialize(address owner_) external;
8+
9+
function executeShortcut(
10+
bytes32 accountId,
11+
bytes32 requestId,
12+
bytes32[] calldata commands,
13+
bytes[] calldata state
14+
)
15+
external
16+
payable
17+
returns (bytes[] memory response);
18+
}
19+

0 commit comments

Comments
 (0)