Notes on Solidity Fridays Episode with Transmissions11
December’s Solidity Fridays with transmissions11 is really excellent, and I think a very high-leverage way to learn how good Solidity actually gets written. When the guest is especially good, as with transmissions, I put Solidity Fridays in the same sort of category as Destroy All Software – teaching by communicating models and patterns of thought, rather than regurgitating tutorial content.
Anyway, here are my notes in case somebody else finds them useful:
When emitting events, t11s emits instances of the contract rather than the address directly; the compiler will swap it out for an address anyway, but this approach gives you greater type safety
Call
CREATE2
to revert if the contract has already been deployed, by adding a salt of the underlying address to the deploymentAddresses are 20 bytes, salts have to be 32 bytes. So we call
fillLast12Bytes
to add the remaining bytes (provided in Solmate’sbytes32
library)The gas cost of
>
is equivalent to!=
when the comparator is0
, more expensive otherwise.Illustration of his opinionated approach to smart contract development: “the performance of the code for users who aren’t stupid matters” - that’s why the [[ERC20]] implementation in [[Solmate]] doesn’t stop you from transferring into the contract’s address. “I’m not raising the cost for everyone else”
fdiv
is like division, but accounting for the bases. ‘Scale this down by the contract’s base’. Multiply the numerator bybaseUnit
and then divide thenumerator * baseUnit
by the denominator. Also checks for overflow onnumerator * baseUnit
, since overflow isn’t protected in assembly calls.In general, keep external calls all the way at the end of the function – including after any events are emitted – to make reentrancy more difficult.
More important with eg ERC777 since eg
safeTransferFrom
might allow arbitrary code execution (https://eips.ethereum.org/EIPS/eip-777)Removing things from the end of an array is significantly cheaper than from the beginning, since in the latter case you have to move everything over.
uint256 currentIndex = withdrawalQueue.length - 1; for (; ; currentIndex--)
is better in this case than initialising theuint56 i = withdrawalQueue.length - 1
since we’re only doing effects (the tx will revert by underflow automatically), so we don’t need to check the length. Saves gas.Add a
trusted
boolean to the strategies, which is then checked on deposit and withdrawal. Makes it easier for EOAs to manage vaults without having to be wrapped in some other contract. Also makes it possible to disable withdrawal from strategies easily if they’re malicious in some way.Two reads to the same struct from
getStrategyData[strategy]
has no extra gas cost, since it gets optimised by the compiler into one singleSLOAD
. & makes it clearer to read by a dev where it’s coming from.Use
unchecked
when you know you won’t underflow or overflow, and so can therefore do without the safety. Saves gas.unchecked
isn’t leaky - it won’t uncheck in nested function callsThe implementation of Compound’s cToken is a little funky, since in a lot of places function calls return an error code rather than revert. So sometimes you need to
require(cToken.blah() == 0)
to ensure it succeeded.