The formal specification is the solidity ABI
All static types are padded to 32 bytes, ie uintXXX, address, bool, etc.
Dynamic types like string, bytes and arrays are encoded in two pieces, a "header" that has a fixed 32 bytes size and a "tail" that contains the data that has variable size.
Tuples and structs, see documentation. Since a tuple can has static and dynamic types it is a mix of both.
Enforcing precise data size can be problematic in general.
For example multisig wallets (ie solidity call) will add data padding, for that reason the check is with greater or equal (
msg.data.length >= size + 4).
It can cause problems if you want to use a function with that modifier from another public function.
function foo(uint a, uint b)
bar(a, b, msg.sender);
function bar(uint a, uint b, address c)
// If called directly it will succeed
// If called from 'foo' it will fail. msg.data are foo parameters only
Some frameworks have removed those checks because it can cause lots of problems to try to maintain them.