1

I have three addresses a1, a2, a3 to which I want to send one third of a contract's balance in the minimal number of transactions (ideally, just one) and without storage. I could have a function as follows:

function withdraw() {
    uint split = this.balance/3;

    a1.send(split);
    a2.send(split);
    a3.send(split);
}

The problem is in regards to atomicity and out-of-gas. As I understand, an out-of-gas exception will revert operations, but send operations are an exception:

When exceptions happen in a sub-call, they “bubble up” (i.e. exceptions are rethrown) automatically. Exceptions to this rule are send and the low-level functions call, delegatecall and callcode – those return false in case of an exception instead of “bubbling up”.

So it is possible for a1 to repeatedly call withdraw() with just enough gas for the first send, thereby draining the contract for itself.

One could try do some mechanism design so that the caller withdraws last. For example:

function withdraw() {
    if(msg.sender != a1 && msg.sender != a2 && msg.sender != a3) return;
    uint split = this.balance/3;

    if(msg.sender != a1) a1.send(split);
    if(msg.sender != a2) a2.send(split);
    if(msg.sender != a3) a3.send(split);

    // msg.sender goes last
    msg.sender.send(split);
}

The problem here is that a1 and a3 can collude to cheat on a2. Indeed, a3 can repeated call withdraw with just enough gas to drain the contract to a1.

I guess it is possible to keep track of withdrawals that have occurred with storage, but that seems like overkill.

Is there a way to make several send operations atomically, similar to how Bitcoin can have multiple UTXOs in a single transaction?

2

You're right about the risk of DoS from one of the recipients.

As a general rule, limit function interactions to one untrusted contract at time.

Think about your withdraw() function as dealing with only one of them at a time. They will individually claim their due.

Something like:

function withdraw(uint amount) returns(bool success) {
  amount = balance[msg.sender]
  balance[msg.sender] = 0;
  if(amount==0) throw;
  LogWithdrawal(msg.sender, amount);
  msg.sender.transfer(amount);
  return true;
}

Hope it helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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