[Proposal] Locking account storage refunds to avoid faucet draining attacks

(Looking for feedback here, I might create a NEP out of it.)

Storage refunds make it very tricky to offer any service that creates Near accounts for users for free. Malicious users can just create many accounts, delete them, and collect the storage refunds.

Zero balance accounts partially address this problem but they only work in limited cases where <= 770 bytes are required. It does not work when a WASM contract needs to be deployed on the users account.

I propose a protocol change that would prevent such faucet-draining attacks even with larger storage requirements, while still allowing dApp providers to pay for their users’ storage.

Summary

  1. When creating an account, the creator can define a sponsor_id. This is an immutable field on the account.
  2. When the account is deleted, we split the storage refund into two parts.
    1. The same amount of tokens as provided for account creation is refunded to the sponsor_id.
    2. The remaining balance goes to beneficiary_id inside the delete action. (Same as today)

note: By default, the sponsor has no way to enforce they get the balance back. Only if the user decides to delete their account will the amount be refunded to the sponsor.

However, using the magic of smart contracts, one could build in a dead-men-switch that allows deleting the account through a smart contract call IF the user becomes inactive for a long time, or even just after a free-tier trial period.

Simpler Alternative

Instead of defining a sponsor, we could also decide the amount of tokens is simply burned, while the user is still allowed to use the storage. This would be slightly simpler to implement.

But making it refundable to a specific sponsor could be useful. In combination with some kind of dead-men-switch to enforce the refund if the user doesn’t convert into a paying user, I believe this could be a much more sustainable business model than just burning the near tokens for all potential users.

But hey, I’m a protocol engineer and not familiar with running a business! So really any thoughts on this alternative are very much welcome. If we can get always with the “always burn” solution, I’m happy to take that route as well.

Detailed Changes Proposed

I will now describe the version wit the sponsor_id in more details to show you the implementation challenges.

The biggest change will be an additional field on CreateAccountAction to specify the sponsor.
This needs to be changed in tools, in the SDK, and even in existing smart contracts.
The most notable smart contract that is relevant here is the linkdrop account deployed on near. It would have to read the sponsor ID as an argument in create_account() and use it when creating a new account.

(Note: If we don’t define a sponsor_id and go with burning tokens instead, we would still need a way to opt-in in the SDK and in the linkdrop contract, so basically we need to change tooling eitherway.)

Then we need to track the sponsor id and the initial balance on the account. We can do it by adding new fields to the account metadata struct.

Populating the initial amount is going to be a bit gimmicky because funding the account happens in a transfer action after the account creation action. (After just the CreateAccountAction the balance is always 0)
To solve this, I propose that we define the initial amount as the amount of tokens on the account at the end of the account creation receipt, which would typically be after the initial transfer and inserting access keys.

You might wonder, why even track the initial balance? As opposed to refund whatever is left? Because the storage staking required can change all the time, up or down. If it goes down (for example because the user deploys a smaller contract) and we don’t keep the initial amount locked, we are back into faucet-draining territory.
Therefore it is important that the initial amount of tokens would remain locked even after state is removed! Which means we have to keep track of what that initial balance was.

Lastly, when deleting an account, we would create two refunds instead of one. Now you might wonder, why not send the entire refund to the sponsor? That would also be a valid choice. I am very curious to hear opinions on this choice. Just remember that the amount refunded covers ALL the Near tokens left on the account, whether they were locked for initial storage, storage added later, or not locked at all.

Please let me know what you think!

8 Likes

Thanks Jakob for writing down this idea! One thought as I think through the use case: it appears that proposal will mostly affect onboarding, where new user accounts are created with a smart contract deployed to help with account recovery. It is then unlikely that any user would somehow end up deleting their account without being prompted (I don’t think account deletion has good support in any wallets). As a result, those tokens are effectively permanently locked without getting removed from the total supply, which feels less ideal than just burning them. That said, I do agree that maybe the contract deployed on users accounts can allow the sponsor to delete the account if it is inactive after a long time (six months for example) and does not hold any asset.

2 Likes

This looks to be a really good proposal and it likely solves almost all of our issues. Thanks for taking the time to come up with/write it down Jakob.

We don’t really need to get the funds back when the user deletes their account. We probably don’t want to be automatically deleting users “worthless” near social profiles if they don’t use them, so the refunds will optimistically be of the order of $100s of NEAR.

The fact you can only set the “Zero Balance” when you create the account is a little awkward. I can see a situation where we’d add some user account feature that would require more space and being a bit scuppered. We can solve this for now by conservatively adding extra NEAR to accounts we create and we can start talking about fixing it after we’ve spent $1,000 on this.

I’d be interested to understand if this entirely subsumes the Zero Balance Account use case. I’m always keen to have fewer concepts in the protocol and this feels like a strictly more flexible version of ZBA’s.

1 Like

I’m planning to move forward with this soon and submit a NEP.

Based on feedback here and offline, I will not include the ability to recover funds to the original sponsor. Anything that’s locked will essentially just be burnt.

I can understand your point but don’t really see an easy way to add more balance later. Adding a new action type seems overkill but I don’t see something simpler. If you have an idea @DavidM-D please let me know. Otherwise, would you also support a solution that cannot change the amount of non-refundable storage?

Good point, I think it would be great to replace ZBAs completely. But I’m afraid people are now using ZBAs and we need to be careful to not break existing usage.

1 Like

@DavidM-D I think there is also a not-terrible workaround for situations where you would need to add more balance later.

Let’s say your initial allocation of e.g. 0.2 NEAR per user was not enough and you decide to increase it to 0.3 NEAR. Existing users can be migrated with a one-time payment to their account of 0.1 NEAR. Even though this 0.1 NEAR is refundable, I think it should be okay as your are only sending it to already established users, which is a fixed number.

WDYT, would that work?

1 Like

That seems completely fine.

Let’s move forward with this :slightly_smiling_face:

1 Like