AllowanceHolder
Inherits: IAllowanceHolder, ReentrancyGuard
Author: Ultrasound Labs
CREATE2-deployed helper that can pull tokens from a user based on a single off-chain permit and
hand out per-subaccount quotas via AllowanceManager.
Note: security-contact: security@ultrasoundlabs.org
State Variables
_OWNER
Holder of the tokens. Stored as immutable to optimise gas and used to compute the CREATE2 salt.
address private immutable _OWNER;
_TOKEN
ERC-20 token managed by this holder.
address private immutable _TOKEN;
_ALLOWANCE_MANAGER
AllowanceManager authorised to call pull.
address private immutable _ALLOWANCE_MANAGER;
Functions
constructor
Deploys a new holder and immediately seeds the first quota / initial transfer.
constructor(
address _owner,
address _token,
address _allowanceManager,
address _subaccount,
uint256 _quotaAmount,
uint256 _timeframe,
uint256 _initialFund,
bytes memory _firstCallData
);
Parameters
| Name | Type | Description |
|---|---|---|
_owner | address | Token holder who signed the permit. |
_token | address | ERC-20 token address the permit refers to. |
_allowanceManager | address | Contract authorised to request pulls and hold quotas. |
_subaccount | address | First subaccount to give spending quota to. |
_quotaAmount | uint256 | Amount of tokens _subaccount may pull per timeframe. |
_timeframe | uint256 | Quota reset window in seconds. |
_initialFund | uint256 | Optional initial transfer to _subaccount executed during construction. |
_firstCallData | bytes | Optional calldata sent to _subaccount (as a normal call) during construction. |
pull
Transfers amount of token() from the owner() directly to subaccount.
Can only be called by allowanceManager().
function pull(address subaccount, uint256 amount) external override nonReentrant;
Parameters
| Name | Type | Description |
|---|---|---|
subaccount | address | The recipient Safe (subaccount). |
amount | uint256 | Amount of tokens to transfer. |
owner
Returns the token owner whose funds can be pulled.
function owner() external view override returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | ownerAddr The owner address. |
token
Returns the ERC-20 token managed by this holder.
function token() external view override returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | tokenAddr The ERC-20 token address. |
allowanceManager
Returns the AllowanceManager that is authorised to call pull.
function allowanceManager() external view override returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | manager The manager address. |
_executeFirstCall
Executes the optional first call on the Safe proxy and returns whether it succeeded.
function _executeFirstCall(address subaccount, bytes memory firstCallData) internal returns (bool innerOk);
Parameters
| Name | Type | Description |
|---|---|---|
subaccount | address | The Safe (subaccount) the call is executed on. |
firstCallData | bytes | Calldata to be forwarded to the subaccount. |
Returns
| Name | Type | Description |
|---|---|---|
innerOk | bool | True if the inner execution succeeded, false otherwise. |
_pull
Internal helper that attempts to transfer amount of _TOKEN from _OWNER to subaccount.
*Slither flags _token.transferFrom(_OWNER, subaccount, amount) as an "arbitrary-from" transfer
(detector slug: arbitrary-send-erc20).
In the context of AllowanceHolder this pattern is expected and safe under
the following assumptions:
_OWNERis an immutable variable that is set exactly once at construction time and never changes afterwards.- Only the associated
AllowanceManagercontract can callpull, which in turn guarantees that internal accounting (spend quotas, non-reentrancy, etc.) is satisfied before any transfer is attempted. - The owner explicitly pre-approved this contract (or granted a Permit2 allowance) during the bootstrap phase, so no third party can trigger an unexpected token movement. Because of these guard-rails the transfer cannot be abused to drain tokens from arbitrary wallets, therefore we silence this specific Slither warning for the remainder of the function.*
Prefers an ERC-20 allowance path; falls back to Permit2 allowance. Does not revert – propagates failures as the returned boolean so the caller can decide whether to emit best-effort events or revert.
function _pull(address subaccount, uint256 amount) internal returns (bool ok);
Parameters
| Name | Type | Description |
|---|---|---|
subaccount | address | The Safe (subaccount) that should receive the funds. |
amount | uint256 | The amount of tokens to transfer. |
Returns
| Name | Type | Description |
|---|---|---|
ok | bool | True if the transfer succeeded, false otherwise. |
Events
Pulled
Emitted when tokens are successfully pulled from the owner to a subaccount.
event Pulled(address indexed subaccount, uint256 indexed amount);
Parameters
| Name | Type | Description |
|---|---|---|
subaccount | address | The Safe (subaccount) that received the tokens. |
amount | uint256 | The amount that has been transferred. |
InitialFundFailed
Emitted when the initial funding transfer during construction fails.
event InitialFundFailed(address indexed subaccount, uint256 indexed amount);
Parameters
| Name | Type | Description |
|---|---|---|
subaccount | address | The Safe (subaccount) that should have received the funds. |
amount | uint256 | The amount that failed to transfer. |
FirstCallFailed
Emitted when the first arbitrary call during construction reverts.
event FirstCallFailed(address indexed subaccount);
Parameters
| Name | Type | Description |
|---|---|---|
subaccount | address | The Safe (subaccount) the call was executed on. |