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 contracts ether balance shouldnt be done.That's because, as you mentionned it's storage duplication, of course, but mainly because it's dangerous to assume that that variable is always gonna be equal to the contracts balance, because it's possible to send ether to a smart contract without calling it to through the SELFDESTRUCT
(that is to be deleted on ETH mainnet in an upcoming fork, but still) opcode, that deletes a smart contracts code and send 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)