2

How does one prevent a contract from accidentally sending Ether to an inactive address? (e.g. the common error is burning ether to 0x0, or a single digit typo in an address)

I've worked on medical devices and have talked to people who work on flight control systems. Such casual errors are prevented by the ecosystem from ever occurring, especially after the Therac-25 tragedy.

How would you do this with the current EVM+Solidity ecosystem?

In general this is called 'defensive programming'.

From the EIP156 discussion, here's a quote:

I'm new here so take this comment with a grain of salt, but I was really surprised to learn Solidity even lets you make transactions that lock out / burn ETH (aside from semantics using some very explicit and intentional opcode). I feel like generations of accountants learned that lesson a long time ago right before they invented double-entry bookkeeping. :-)

3 Answers 3

3

A current balance on an address or prior activity proves nothing. Please don't ever rely on such data.

Use the Withdraw Pattern

The withdraw pattern is a two-step process for sending Ether to an address:

  1. Record a balance for the receiving address. For example, assuming you have a mapping of balances:

    mapping (address => uint) balances;

    Send Ether by recording a balance for that address:

    balances[address] += amount;

  2. In a separate transaction, the receiving address withdraws Ether up to the balance of its address using msg.sender.transfer(). msg.sender is crucial here.

The withdraw pattern prevents ever sending to 0x0 or an address with a typo. Instead, a balance is recorded, and the receiver must withdraw the balance. Since the contract only ever sends to msg.sender, Ether can never be sent to an inactive address.

You're probably wondering, though, what happens if a balance is recorded for an inactive address? Doesn't that lock the Ether in the contract, which is essentially the same problem as sending to an inactive address? This leads us to...

Include an Undo Mechanism

If Ether is held in a contract balance for an inactive address, but not actually sent, there are a few ways you can deal with it. Since sending Ether with the withdraw pattern is a two-step process, if the second step hasn't occurred yet, oftentimes reverting the first step is exactly what you want.

Of course, whether or not reverting the first step is acceptable is application specific. But you can (and should) include the mechanism if it doesn't reduce the value-proposition of the contract itself. Your mileage may vary, but it's highly recommended that you include one, if not both, of the following.

  1. An explicit undo mechanism in the contract that the sender can call. Since the first step entails incrementing a balance, you can undo that by simply decrementing the balance and moving it wherever it should go. If the receiving address tries to withdraw now, it simply fails.
  2. A timeout. Whenever you can, include a hard time limit, beyond which, funds cannot be withdrawn or they're returned to the sender. Since you sent by simply incrementing a balance, you can also record the time using block.timestamp (aliased to now in Solidity). Then, a revert-after-timeout function can be called by the sender to undo the operation.

    For example, for payment channels, the timeout may be on the order of a day or a week. (Credit card authorizations usually lock funds for a week. If the funds aren't captured by the merchant within that time, they're released back to the payer.) Other things may be on the order of months or years. But it's rare that things need to be held indefinitely. As long as all parties are aware of the timeout, it prevents losing funds to inactive addresses due to typos or other mistakes.

See Ethereum Payment Channel in 50 Lines of Code for an example of using a timeout.

For more defensive programming techniques, see Solidity's security considerations and common patterns.

4
  • excellent answer, thank you. Now to check and see if the standard wallets support this...
    – Paul S
    Commented Nov 16, 2017 at 17:59
  • so, what happens if the default function of msg.sender is a function that recursively calls back to withdraw? Is it proof against the recursion attack?
    – Paul S
    Commented Nov 16, 2017 at 21:05
  • That's a separate issue. You'll want to use the checks-effects-interactions pattern. Basically, validate all input first, then do any side-effects like updating balances, and only send or interact with other contracts last. If you follow this pattern, a call back to withdraw will fail since the balance has already been updated. Assume that interacting with any other address is unsafe and will call back to you. Mark your functions internal where appropriate. Commented Nov 16, 2017 at 23:45
  • yikes, so complicated. I note the example didn't check the return value of transfer, which means that the balance update will be wrong if OOG happens on the transfer...
    – Paul S
    Commented Nov 17, 2017 at 2:51
2

Just to echo the other answer...

Addresses which have never been used

I can generate (an Ethereum) public/private key pair offline, without any connection to the Ethereum network. No one will know that I've generated them.

I can then generate an account address from my public key, still with no connection to the network. I now "own" this address, regardless of the network knowing about it or not. (See: How are ethereum addresses generated?)

Until I use the address, the network won't know about it. But the address, along with all other addresses in the space, exists. They're just waiting for someone to generate (and own) their private key.

Used but inactive addresses

Once I've used my address, you know about it - it will appear in the state data. (See: Ethereum block architecture) This is whether it has a zero or non-zero balance. You can trawl through the state data yourself, or check addresses on block explorers.

However, if I lose my private key the address effectively lost forever. (There's a non-zero chance someone will generate the private key again, but it's basically zero.) You will have no way of knowing I've lost my private, so you will have no way of knowing if the account is genuinely lost/inactive or if I just haven't used it for a while.


So...

How do you prevent accidentally sending Ether to an inactive address?

You can't. There's no way of knowing if:

  1. Someone owns the private key to an address that has never been used, and
  2. A previously used account is genuinely inactive/lost.
5
  • For defensive coding, you would want to detect used but inactive addresses in the state data from the EVM (i.e. from Solidity). Can you do that, or can you only do it outside the blockchain?
    – Paul S
    Commented Nov 10, 2017 at 22:46
  • Also I think you missed one the scenarios being addressed in Homestead - mistyped addresses: github.com/ethereum/EIPs/issues/55
    – Paul S
    Commented Nov 10, 2017 at 22:53
  • You can check if an address exists in the state data, meaning that it has been used, and someone (at some point) owned the private key. However, if they've lost the private key, and the address can never be used again, you will never know. So you can't defend against these scenarios. You can check if the address has a valid format, which is EIP-55 (and others?) - that's relatively straightforward. This only protects against certain types of mistyped addresses though, not all of them. (i.e. It prevents you mistyping an address with an invalid format/pattern.) Commented Nov 10, 2017 at 23:00
  • What's the EVM or Solidity instruction to check if the address exists in the state data? I think I'm not getting across I'm trying to have defensive programming for inter-contract calls, sends and dependencies, which is the cause of last year's hard-fork theDao fiasco and this year's wallet fiasco. This question is only for the inter-contract Send scenario as well as human-typed sends from a wallet. (the latter being addressed in Homestead)
    – Paul S
    Commented Nov 10, 2017 at 23:02
  • Here'e a reference that says 'change to a system where caller asks for the money', which guarantees the address is valid (it's msg.sender) but is pretty complicated: hackingdistributed.com/2016/06/16/…
    – Paul S
    Commented Nov 11, 2017 at 0:00
0

I don't think there's a way to determine whether or not an address is an "inactive" address, of course that's assuming it's a valid format (0x with 40 hex characters).

I think all addresses are technically valid and can receive Ether since the addresses are cryptographically generated.

I suppose all addresses start off as "inactive"/never been used before and need to receive Ether in the first place to begin using that address.

1
  • Can you see another account's balance? I note that a stranded account is inactive yet as the current and past grief shows, has a non-zero balance....
    – Paul S
    Commented Nov 10, 2017 at 7:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.