Tengo tres direcciones a1
, a2
, a3
a las que quiero enviar un tercio del saldo de un contrato en el mínimo número de transacciones (idealmente una sola) y sin almacenamiento. Podría tener una función de la siguiente manera:
function withdraw() {
uint split = this.balance/3;
a1.send(split);
a2.send(split);
a3.send(split);
}
El problema está relacionado con la atomicidad y la falta de gas. Según tengo entendido , una excepción de falta de gas revertirá las operaciones, pero send
las operaciones son una excepción:
Cuando se producen excepciones en una subllamada, "surgen" (es decir, las excepciones se vuelven a generar) automáticamente. Las excepciones a esta regla son
send
y las funciones de bajo nivelcall
,delegatecall
ycallcode
– aquellas regresanfalse
en caso de una excepción en lugar de “burbujear”.
Por lo tanto, es posible a1
llamar repetidamente withdraw()
con suficiente gasolina para el primero send
, agotando así el contrato por sí mismo.
Se podría intentar hacer algún diseño de mecanismo para que la persona que llama se retire en último lugar. Por ejemplo:
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);
}
El problema aquí es que a1
y a3
puede coludirse para hacer trampa a2
. De hecho, a3
puede repetir la llamada withdraw
con la gasolina suficiente para agotar el contrato a1
.
Supongo que es posible realizar un seguimiento de los retiros que se han producido con el almacenamiento, pero eso parece una exageración.
¿Hay alguna forma de realizar varias send
operaciones de forma atómica, similar a cómo Bitcoin puede tener múltiples UTXO en una sola transacción?
Tiene razón sobre el riesgo de DoS de uno de los destinatarios.
Como regla general, limite las interacciones de funciones a un contrato que no sea de confianza a la vez.
Piense en su withdraw()
función como si tratara solo con uno de ellos a la vez. Reclamarán individualmente lo que les corresponde.
Algo como:
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;
}
Espero eso ayude.