Skip to content

Update TBA Contract #459

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

Merged
merged 5 commits into from
Aug 17, 2023
Merged
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
162 changes: 153 additions & 9 deletions contracts/token-bound-account/TokenBoundAccount.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
/* solhint-disable reason-string */

// Base
import "../smart-wallet/utils/BaseAccount.sol";

// Extensions
import "../extension/Multicall.sol";
import "../dynamic-contracts/extension/Initializable.sol";
import "../dynamic-contracts/extension/ContractMetadata.sol";
import "../openzeppelin-presets/token/ERC721/utils/ERC721Holder.sol";
import "../openzeppelin-presets/token/ERC1155/utils/ERC1155Holder.sol";
import "../eip/ERC1271.sol";

// Utils
import "../openzeppelin-presets/utils/cryptography/ECDSA.sol";
import "../smart-wallet/utils/BaseAccountFactory.sol";

import "./erc6551-utils/ERC6551AccountLib.sol";
import "./erc6551-utils/IERC6551Account.sol";

import "../eip/interface/IERC721.sol";
import "../smart-wallet/non-upgradeable/Account.sol";
import "../openzeppelin-presets/utils/cryptography/EIP712.sol";

contract TokenBoundAccount is Account, IERC6551Account {
contract TokenBoundAccount is
Initializable,
ERC1271,
Multicall,
BaseAccount,
ContractMetadata,
ERC721Holder,
ERC1155Holder,
IERC6551Account,
EIP712
{
using ECDSA for bytes32;

/*///////////////////////////////////////////////////////////////
Expand All @@ -17,7 +46,17 @@ contract TokenBoundAccount is Account, IERC6551Account {
event TokenBoundAccountCreated(address indexed account, bytes indexed data);

/*///////////////////////////////////////////////////////////////
Constructor
State
//////////////////////////////////////////////////////////////*/

/// @notice EIP 4337 factory for this contract.
address public immutable factory;

/// @notice EIP 4337 Entrypoint contract.
IEntryPoint private immutable entrypointContract;

/*///////////////////////////////////////////////////////////////
Constructor, Initializer, Modifiers
//////////////////////////////////////////////////////////////*/

/**
Expand All @@ -27,14 +66,22 @@ contract TokenBoundAccount is Account, IERC6551Account {
* @param _factory - The factory contract address to issue token Bound accounts
*
*/
constructor(IEntryPoint _entrypoint, address _factory) Account(_entrypoint, _factory) {
constructor(IEntryPoint _entrypoint, address _factory) EIP712("TokenBoundAccount", "1") {
_disableInitializers();
factory = _factory;
entrypointContract = _entrypoint;
}

receive() external payable override(IERC6551Account, Account) {}
// solhint-disable-next-line no-empty-blocks
receive() external payable virtual {}

/// @notice Initializes the smart contract wallet.
function initialize(address _defaultAdmin, bytes calldata _data) public virtual initializer {
emit TokenBoundAccountCreated(_defaultAdmin, _data);
}

/// @notice Returns whether a signer is authorized to perform transactions using the wallet.
function isValidSigner(address _signer, UserOperation calldata) public view override returns (bool) {
function isValidSigner(address _signer, UserOperation calldata) public view returns (bool) {
return (owner() == _signer);
}

Expand Down Expand Up @@ -70,7 +117,7 @@ contract TokenBoundAccount is Account, IERC6551Account {
}

/// @notice Withdraw funds for this account from Entrypoint.
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public virtual override {
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public virtual {
require(owner() == msg.sender, "Account: not NFT owner");
entryPoint().withdrawTo(withdrawAddress, amount);
}
Expand All @@ -91,6 +138,50 @@ contract TokenBoundAccount is Account, IERC6551Account {
return getNonce();
}

/// @notice See {IERC165-supportsInterface}.
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155Receiver) returns (bool) {
return
interfaceId == type(IERC1155Receiver).interfaceId ||
interfaceId == type(IERC721Receiver).interfaceId ||
super.supportsInterface(interfaceId);
}

/// @notice Returns the EIP 4337 entrypoint contract.
function entryPoint() public view virtual override returns (IEntryPoint) {
return entrypointContract;
}

/// @notice Returns the balance of the account in Entrypoint.
function getDeposit() public view virtual returns (uint256) {
return entryPoint().balanceOf(address(this));
}

/// @notice Executes a transaction (called directly from an admin, or by entryPoint)
function execute(
address _target,
uint256 _value,
bytes calldata _calldata
) external virtual onlyAdminOrEntrypoint {
_call(_target, _value, _calldata);
}

/// @notice Executes a sequence transaction (called directly from an admin, or by entryPoint)
function executeBatch(
address[] calldata _target,
uint256[] calldata _value,
bytes[] calldata _calldata
) external virtual onlyAdminOrEntrypoint {
require(_target.length == _calldata.length && _target.length == _value.length, "Account: wrong array lengths.");
for (uint256 i = 0; i < _target.length; i++) {
_call(_target[i], _value[i], _calldata[i]);
}
}

/// @notice Deposit funds for this account in Entrypoint.
function addDeposit() public payable virtual {
entryPoint().depositTo{ value: msg.value }(address(this));
}

/*///////////////////////////////////////////////////////////////
Internal Functions
//////////////////////////////////////////////////////////////*/
Expand All @@ -99,7 +190,7 @@ contract TokenBoundAccount is Account, IERC6551Account {
address _target,
uint256 value,
bytes memory _calldata
) internal virtual override returns (bytes memory result) {
) internal virtual returns (bytes memory result) {
bool success;
(success, result) = _target.call{ value: value }(_calldata);
if (!success) {
Expand All @@ -109,12 +200,65 @@ contract TokenBoundAccount is Account, IERC6551Account {
}
}

/// @notice Validates the signature of a user operation.
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash)
internal
virtual
override
returns (uint256 validationData)
{
bytes32 hash = userOpHash.toEthSignedMessageHash();
address signer = hash.recover(userOp.signature);

if (!isValidSigner(signer, userOp)) return SIG_VALIDATION_FAILED;
return 0;
}

function getFunctionSignature(bytes calldata data) internal pure returns (bytes4 functionSelector) {
require(data.length >= 4, "Data too short");
return bytes4(data[:4]);
}

function decodeExecuteCalldata(bytes calldata data) internal pure returns (address _target, uint256 _value) {
require(data.length >= 4 + 32 + 32, "Data too short");

// Decode the address, which is bytes 4 to 35
_target = abi.decode(data[4:36], (address));

// Decode the value, which is bytes 36 to 68
_value = abi.decode(data[36:68], (uint256));
}

function decodeExecuteBatchCalldata(bytes calldata data)
internal
pure
returns (
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _callData
)
{
require(data.length >= 4 + 32 + 32 + 32, "Data too short");

(_targets, _values, _callData) = abi.decode(data[4:], (address[], uint256[], bytes[]));
}

/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view virtual override returns (bool) {
return msg.sender == owner();
}

/*///////////////////////////////////////////////////////////////
Modifiers
//////////////////////////////////////////////////////////////*/

modifier onlyAdminOrEntrypoint() override {
modifier onlyAdminOrEntrypoint() {
require(msg.sender == address(entryPoint()) || msg.sender == owner(), "Account: not admin or EntryPoint.");
_;
}

modifier onlyAdmin() {
require(msg.sender == owner(), "Account: not admin.");
_;
}
}