2

Beginner question:

In one or two smart contracts samples, I've seen that a function is payable but the smart contract seems to hold its own state on the total it has received, e.g. total += msg.value.

I'm trying to learn and build a mental model of what is happening. In my mind, the blockchain has its native currency which provides the means for a payable function, which I assume is the transfer of value from an address like a wallet to another like a contract.

The blockchain holds the facts of these transfers. So why would a smart contract keep track of how much has been received?

This duplicates state and is WET code (latent bugs); I assume there's no built-in function to ask the blockchain what the balance is for a particular contract, else why would one track it and open up the chance of programmer error?

I'd be grateful if someone could explain what I've misunderstood, thank you.

2 Answers 2

4

TL;DR You could have a variable in your contract that keeps track of how much ether it has received in total through a function, if you're for example managing a payment channel and want to keep historical data in your contracts state, but you should never assume it's gonna be actually equal to your contracts balance, and favor using the native way of getting an the balance of an address instead

I assume there's no built-in function to ask the blockchain what the balance is for a particular contract

There is one, actually. It's address().balance in solidity, and more generally the BALANCE (0x31) or SELFBALANCE (0x47) opcodes.

You're not providing context, but maybe those smart contracts can have ether transferred out of them, at some point, and still want to keep track of the total amount they received regardless of what they had transferred out for, some reason?

Now, you're perfectly right that maintaining a storage variable that is supposed to track the contract's ether balance shouldn't be done. That's because, as you mentioned, it's storage duplication, of course, but mainly because it's dangerous to assume that the variable is always gonna be equal to the contract's balance.

It's possible to send ether to a smart contract without calling it through SELFDESTRUCT opcode (that is to be deleted on ETH mainnet in an upcoming fork, but still). This opcode deletes a smart contract and sends all its balance to another address, without actually calling it.

Let's take those 2 smart contracts.

contract VulnerableBank {
  uint balance;
  // Sending Ether to the contract will trigger that function
  receive() external payable {
    balance += msg.value;
  }
  function withdraw() external {
    // Left is our own variable that keeps track of the contracts balance, right is the native way of doing it
    require(balance == address(this).balance) 
    payable(msg.sender).transfer(balance);
    balance = 0;
  }
}

contract Griefer {
  function grief(address payable to) external payable {
    selfdestruct(to);
  }
}

If grief() is called with the VulnerableBank contract address as input, and Griefer's balance != 0, we're going to break the sync between VulnerableBank's balance variable and its actual balance, effectively bricking the contract by making the withdraw() function uncallable.

1
  • TIL that there's another way to send ether to a contract in addition to using selfdestruct. A contract's address is deterministically computed from the creator's address the nonce of the creation transaction, so an attacker can prefund the contract just before it's created. To Foxxxey's point, any invariant checks that ensure/assume the contract balance as recorded on the blockchain and any tracking done internally, are a point of failure that an attacker can exploit to cause the contract to misbehave or permanently break, so funds get stuck. Mar 26 at 20:17
0

I want to answer my own question with what I've learned since.

Using Blockchain Data

Contracts using the blockchain's record of ether sent to its address via e.g. this.balance open themselves up to attack.

My comment above and the answer by Foxxxey allude to this in that code that is contingent on the contract's balance matching any internal record of the ether sent to it can be made to misbehave by having ether "forcibly" sent to the contract address via unusual means.

However, it's not just checks, but more subtle bugs may exist when this.balance conspires with other code, or where code depends on exact values of a contract's balance, or even where unchecked math operations may over or underflow a value.

It seems to me that because ether can be sent to a contract without invoking contract code, that it is actually safer NOT to rely upon or indeed use the blockchain's balance data at all, but only your own thoroughly checked code.

Pots

mapping(address => uint256) balances;

This is needed for internal accounting: it's a form of sub-account. I think it's better to call the field pots as it sets up a more concrete mental model.

Without a concept of pots it's impossible to record who's money you're spending, if your contract has such a need.

The blockchain records facts about the ether that has been transferred between addresses, so it's reasonable to conclude that you can use the official record to query the "balance" a sender has with your contract.

However, if your contract spends ether, then who's ether is it spending?

It's possible that your design is simply that all ether is pooled and spending is from the pool. But if not, then you must add to and subtract from the balance of everyone's pot.

I'm not sure whether this is obvious to beginners, i.e. that the blockchain has no built-in concept of sub-accounts or "pots", and why should it. Example contracts often don't include spending code and so tracking who sent what appears strange given that this is the fundamental job of the blockchain itself.

If your contract only ever receives ether from an address and sends it back to the same address, and never anywhere else, then I would argue that the mapping field is superfluous, since the blockchain should know the total ether sent from x to y and from y to x with the balance being the difference. I'm not sure what kind of contract would do such a thing.

Your Answer

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

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