DEV Community

Przemyslaw Rzad
Przemyslaw Rzad

Posted on

Destructible Transfer pattern

If you are a Solidity developer like me, the Withdrawal from Contracts pattern is probably one of the first Common Patterns you learn. It can protect your contract from failing when sending funds to another address. However, the trade-off is that there is an additional step introduced, which increases complexity and can disturb the automation process.

☝️ If you are not familiar with the Withdrawal from Contracts pattern, read on. Otherwise, you can skip to the Destructible Transfer pattern section.

Case Study - Payroll system

Imagine you are developing an automatic Payroll system which sends payments in Ether. All the hard parts are done - the only thing left to do is to write a Smart Contract that will process and log each payment on the Ethereum blockchain by emitting an event. Here is your first draft:

contract NaivePayroll {
    event Paid(address account, uint amount);

    function pay(address account) public payable {
        require(account.send(msg.value));
        emit Paid(account, msg.value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Security risk

The contract above is named NaivePayroll for a reason - it naively assumes that the receiving address is either a wallet, or a contract that won’t do anything weird. Consider the following contract:

contract MaliciousReceiver {
    function() public payable {
        revert();
    }
}
Enter fullscreen mode Exit fullscreen mode

The unnamed function is a fallback function - it gets executed whenever there is an Ether transfer to this contract.

💥 If the MaliciousReceiver is on the payroll, your automation system will be sending failing transactions to the blockchain.

The solution

You designed the automation system to either skip these accounts or gracefuly handle the failing transactions. However, this is not enough - the intent to pay must be logged on the blockchain, and the funds must leave the automation system’s account.

There are two solutions to this problem:

  • Withdrawal from Contracts pattern
  • Destructible Transfer pattern

Withdrawal from Contracts pattern

You quickly change your NaivePayroll using the Withdrawal from Contracts pattern. Here’s your code:

contract BrokePayroll {
    mapping(address => uint) pending;
    event PayAdded(address account, uint amount);
    event PayWithdrawed(address account, uint amount);

    function pay(address account) public payable {
        pending[account] = pending[account] + msg.value;
        emit PayAdded(account, msg.value);
    }

    function withdrawPay() public {
        require(msg.sender.send(pending[msg.sender]));
        emit PayWithdrawed(msg.sender, pending[msg.sender]);
        pending[msg.sender] = 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

Security risk

Again, the name of the contract is not without reason. Consider the following contract:

contract GreedyReceiver {
    function getPaid(address _payroll) public {
        Payroll payroll = Payroll(_payroll);
        payroll.withdrawPay();
    }

    function() public payable {
        if (msg.sender.balance >= msg.value) {
            this.getPaid(msg.sender);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

💸 If the GreedyReceiver is on the payroll, it can withdraw more funds than intended.

The fallback function of the GreedyReceiver initiaties a withdrawal again and again, until there are no funds left and you are broke.

☝️ This problem is referred to as the Reentrancy problem

Correct Withdrawal from Contracts pattern usage

The solution to reentrancy is rather simple - all you need to do is to clear the pending withdrawals first, before sending the funds.

contract WithdrawalPatternPayroll {
    mapping(address => uint) pending;
    event PayAdded(address account, uint amount);
    event PayWithdrawed(address account, uint amount);

    function pay(address account) public payable {
        pending[account] = pending[account] + msg.value;
        emit PayAdded(account, msg.value);
    }

    function withdrawPay() public {
        uint value = pending[msg.sender];
        pending[msg.sender] = 0;
        msg.sender.send(value);
        emit PayWithdrawed(msg.sender, value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Destructible Transfer pattern

The alternative to using the Withdrawal from Contracts pattern is to use the Destructible Transfer.
The core of this pattern is a small but interesting contract:

contract DestructibleTransfer {
    constructor(address payee) public payable {
        selfdestruct(payee);
    }
}
Enter fullscreen mode Exit fullscreen mode

The DestuctibleTransfer contract acts as an intermediary contract used for conducting a payment. When constructing, it immediately self-destructs, which causes the remaining funds to be transferred to a given address.
The DestuctibleTransferPayroll contract looks like this:

contract DestuctibleTransferPayroll {
    event Paid(address account, uint amount);

    function pay(address account) public payable {
        (new DestructibleTransfer).value(msg.value)(account);
        emit Paid(account, msg.value);
    }
}
Enter fullscreen mode Exit fullscreen mode

The DestuctibleTransferPayroll contract is not vulnerable to both MaliciousReceiver and GreedyReceiver.

Why it works

The transfer initiated from a destructing contract does not execute the reciver’s fallback function.
Therefore, failures or reentrancy in the fallback function code are no match for our Payroll system utilising the Destructible Transfer pattern.

Downsides

The Destructible Transfer is using more gas than the ordinary transfer. In order to reduce gas usage, consider using the Destructible Transfer only when the standard transfer fails.

contract SmartPayroll {
    event Paid(address account, uint amount);

    function pay(address account) public payable {
        if (!account.send(msg.value)) {
            (new DestructibleTransfer).value(msg.value)(account);
        }
        emit Paid(account, msg.value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In Smart Contract development, security is very important, especially when dealing with Ether deposits and withdrawals. Every security breach puts a stain on community’s trust in the Ethereum’s applicability for real world businesses.

I believe that estabilishing Solidity patterns and best practices will improve general quality of Smart Contracts on the Ethereum blockchain, and hope that my contribution will help the developers to write secure Smart Contracts with confidence.

Top comments (0)