The addresses can be dynamically generated if you use CREATE2. An O(1) fowarder would just look like:
delegatecall(hash(0xff + self + mload(0) + hash(init_code))).
An average contract can have ~50 functions (including internal functions)
You don’t necessarily need one contract per function; if you want to save on contract count, then you can group functions as well. The point is that this would be HLL-level pagination, and regardless of the page size the forwarder can be made to be O(1).
Even ERC20 tokens with a lot of holders won’t fit in the remaining space.
The goal is that per-user storage (or really any storage that is not O(1)) would live in separate contracts (see eg. the examples here: https://ethresear.ch/t/common-classes-of-contracts-and-how-they-would-handle-ongoing-storage-maintenance-fees-rent/4441). This is a good idea anyway because it makes rent accounting much cleaner; the storage belonging to a particular beneficiary is stored in a contract that the beneficiary themselves is responsible for paying the ETH to kep up. Rent/hibernation/waking schemes that keep the current monolithic O(N)-sized storage tree model tend to be much more complicated.
Needing to make an extra external call (delegatecall) is. It will increase the cost of an average erc20 transfer by 5-10% (~2500 gas).
This is an artefact of present-day gas costs, not necessarily a reflection of costs in reality. If you go back to the discussion in 2016 that led to the current 700 gas cost for delegatecall (see eg. https://github.com/ethereum/EIPs/issues/150), one of the competing proposals (option 2) was effectively a gas cost that scales with contract size. If that had been implemented, then modular contract structures would be favored because they would not load parts of code that don’t need to be accessed, so the total gas cost of making calls would be lower.
Additionally, delegatecall is expensive in part because of known weaknesses in the current gas cost model (eg. self-calling, and calling a contract that was already called in the same block, are gas-expensive despite being cheap in reality) which should not be taken as a given; if we are making changes to the code these isuses can be remedied.
If eth1.x is going in a stateless client direction then the byte size of contracts becomes a large cost component, and so we should adopt version 2 of EIP 150 as that would more accurately reflect costs, and we should favor contract modularity. If eth1.x is not going in a stateless client direction (ie. it’s doing rent), then the cost of loading large amounts of code is lower; I suppose we would ask the experts to determine whether the per-page loading cost is sufficiently high that favoring contract modularity is optimal. If loading any amount of data up until a few dozen kilobytes is basically O(1) and we’re not doing stateless clients, then I would agree that bumping up the max contract size is optimal.