Skip to content
Open
Show file tree
Hide file tree
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
30 changes: 15 additions & 15 deletions snapshots/BenchmarkTest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
"testERC20Transfer_ERC4337MinimalAccount": "171509",
"testERC20Transfer_ERC4337MinimalAccount_AppSponsor": "168500",
"testERC20Transfer_ERC4337MinimalAccount_ERC20SelfPay": "199831",
"testERC20Transfer_IthacaAccount": "128059",
"testERC20Transfer_IthacaAccountWithSpendLimits": "193602",
"testERC20Transfer_IthacaAccount_AppSponsor": "138595",
"testERC20Transfer_IthacaAccount_AppSponsor_ERC20": "143884",
"testERC20Transfer_IthacaAccount_ERC20SelfPay": "128548",
"testERC20Transfer_IthacaAccount": "128232",
"testERC20Transfer_IthacaAccountWithSpendLimits": "193695",
"testERC20Transfer_IthacaAccount_AppSponsor": "138724",
"testERC20Transfer_IthacaAccount_AppSponsor_ERC20": "144025",
"testERC20Transfer_IthacaAccount_ERC20SelfPay": "128721",
"testERC20Transfer_Safe4337": "197561",
"testERC20Transfer_Safe4337_AppSponsor": "191725",
"testERC20Transfer_Safe4337_ERC20SelfPay": "221464",
Expand All @@ -29,25 +29,25 @@
"testERC20Transfer_ZerodevKernel_ERC20SelfPay": "235489",
"testERC20Transfer_batch100_AlchemyModularAccount": "10109066",
"testERC20Transfer_batch100_AlchemyModularAccount_ERC20SelfPay": "11609298",
"testERC20Transfer_batch100_IthacaAccount": "7531912",
"testERC20Transfer_batch100_IthacaAccount_AppSponsor": "8140216",
"testERC20Transfer_batch100_IthacaAccount_AppSponsor_ERC20": "7966120",
"testERC20Transfer_batch100_IthacaAccount_ERC20SelfPay": "7353064",
"testERC20Transfer_batch100_IthacaAccount": "7549092",
"testERC20Transfer_batch100_IthacaAccount_AppSponsor": "8152996",
"testERC20Transfer_batch100_IthacaAccount_AppSponsor_ERC20": "7979008",
"testERC20Transfer_batch100_IthacaAccount_ERC20SelfPay": "7370352",
"testERC20Transfer_batch100_ZerodevKernel": "12631318",
"testERC20Transfer_batch100_ZerodevKernel_ERC20SelfPay": "14149937",
"testNativeTransfer_AlchemyModularAccount": "180829",
"testNativeTransfer_CoinbaseSmartWallet": "178916",
"testNativeTransfer_IthacaAccount": "129415",
"testNativeTransfer_IthacaAccount_AppSponsor": "139982",
"testNativeTransfer_IthacaAccount_ERC20SelfPay": "137204",
"testNativeTransfer_IthacaAccount": "129588",
"testNativeTransfer_IthacaAccount_AppSponsor": "140111",
"testNativeTransfer_IthacaAccount_ERC20SelfPay": "137377",
"testNativeTransfer_Safe4337": "198595",
"testNativeTransfer_ZerodevKernel": "208635",
"testUniswapV2Swap_AlchemyModularAccount": "238647",
"testUniswapV2Swap_CoinbaseSmartWallet": "237451",
"testUniswapV2Swap_ERC4337MinimalAccount": "230691",
"testUniswapV2Swap_IthacaAccount": "187179",
"testUniswapV2Swap_IthacaAccount_AppSponsor": "197691",
"testUniswapV2Swap_IthacaAccount_ERC20SelfPay": "192480",
"testUniswapV2Swap_IthacaAccount": "187376",
"testUniswapV2Swap_IthacaAccount_AppSponsor": "197832",
"testUniswapV2Swap_IthacaAccount_ERC20SelfPay": "192665",
"testUniswapV2Swap_Safe4337": "257333",
"testUniswapV2Swap_ZerodevKernel": "266367"
}
81 changes: 80 additions & 1 deletion src/IthacaAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
mapping(bytes32 => LibStorage.Bump) keyExtraStorage;
/// @dev Nonce management when porto account acts as paymaster.
mapping(bytes32 => bool) paymasterNonces;
/// @dev Mapping of approved spends to their IDs
mapping(uint256 => Spend) spends;
}

/// @dev Returns the storage pointer.
Expand Down Expand Up @@ -139,6 +141,9 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
/// @dev The paymaster nonce has already been used.
error PaymasterNonceError();

/// @dev The spend has expired.
error Expired();

////////////////////////////////////////////////////////////////////////
// Events
////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -592,6 +597,80 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
if (!$.keyHashes.remove(keyHash)) revert KeyDoesNotExist();
}

////////////////////////////////////////////////////////////////////////
// Spend/SubAccount Functions
////////////////////////////////////////////////////////////////////////

struct Spend {
/// @dev If the spender is a parent, then it can sweep ALL funds from the account.
bool isParent;
/// @dev The expiry of the spend.
uint32 expiry;
/// @dev The `msg.sender` which is calling spend.
address spender;
/// @dev Mapping of tokens to their spending limits.
mapping(address token => uint256 limit) limits;
}

/// @dev This function can be used to give safe spending permissions to any entity.
/// In the `parentAccount` <> `subAccount` architecture, this function is used for -
/// * In the parent account, subaccounts can use this function to JIT pull funds.
/// * In the sub account, parent accounts can use this function to sweep back leftover funds.
/// Note: This spend function only supports ERC20 tokens.
/// We will have to create a spend function for all types of tokens we want to support.
function spend(
uint256 spendId,
address[] calldata tokens,
uint256[] calldata amounts,
address recipient
) public payable virtual {
Spend storage _spend = _getAccountStorage().spends[spendId];

// Spend can only be called by the authorized spender
if (msg.sender != _spend.spender) {
revert Unauthorized();
}

// If the spender is a parent, then it can sweep any amount of funds from the account.
if (_spend.isParent) {
for (uint256 i = 0; i < tokens.length; i++) {
TokenTransferLib.safeTransfer(tokens[i], recipient, amounts[i]);
}
} else {
if (block.timestamp > _spend.expiry) {
revert Expired();
}

for (uint256 i = 0; i < tokens.length; i++) {
address token = tokens[i];
// Ensure that the spending is within the specified limits
_spend.limits[token] -= amounts[i];

TokenTransferLib.safeTransfer(token, recipient, amounts[i]);
}
}
}

/// @dev Sets the spend for the `spendId`.
/// Note: Values are overridden for existing spends, instead of adding.
function setSpend(
uint256 spendId,
bool isParent,
uint32 expiry,
address spender,
address[] calldata tokens,
uint256[] calldata limits
) public virtual onlyThis {
Spend storage _spend = _getAccountStorage().spends[spendId];

_spend.isParent = isParent;
_spend.expiry = expiry;
_spend.spender = spender;
for (uint256 i = 0; i < tokens.length; i++) {
_spend.limits[tokens[i]] = limits[i];
}
}

////////////////////////////////////////////////////////////////////////
// Orchestrator Functions
////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -755,6 +834,6 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
returns (string memory name, string memory version)
{
name = "IthacaAccount";
version = "0.5.10";
version = "0.5.11";
}
}
Loading