Many recent works aiming to detect vulnerabilities in smart contracts written in So- lidity have been presented over the last few months. Most of them investigate on the reentrancy bug, the one behind the DAO attack [47], or on Solidity’s way to deal with exceptions. Some do so analyzing the EVM bytecode [31][2]; others look at the prob- lem from a concurrent point of view [44] or compile down Solidity code into lan- guages with more powerful type systems in order to benefit from their expressiveness [8]. Atzei, Bartoletti, and Cimoli [6] provide a survey of attacks and vulnerabilities in Solidity code. Alongside reentrancy and exceptions are many other subtle bugs that can make a contract misbehave. To the best of our knowledge, there are no works investigating the type safety of casts and transfers. As we said, aborting transactions containing incorrect casts or transfers does not harm any account, but the sender of the transaction itself. A malicious contract can clumsily implement such operations with no other aim of making a miner richer and to make the accounts initiating the transaction waste money. On the other hand, a sound contract would have all the inter- est in providing an implementation enhanced with some additional safety guarantees. Example 7.3 clarifies such a scenario.
Example 7.3 (Cast and transfer vulnerabilities in smart contracts). Consider the con- tract Unsafe in Listing 7.3.
contract A { A(){} uintf (){ return 5; } } contract U nsaf e{
mapping(address ⇒ uint) balances;
U nsaf e(mapping(address ⇒ uint) balances) { this.balances = balances;
}
unit deposit() {
return this.balances[msg.sender → this.balances[msg.sender] + msg.value]; unit }
uint unsaf eCast() { return A(msg.sender).f (); }
unit unsaf eW ithdraw() {
return uint b = this.balances[msg.sender]; this.balances[msg.sender → 0]; msg.sender.transfer(b); unit
} }
Listing 7.3: Unsafe transfer and cast operations
It implements a basic form of the withdrawal pattern2allowing users to deposit and afterwards withdraw an amount of Ether. Suppose this contract has an invariant stating that a user can always withdraw the amount of Ether they have deposited so far. The problem here is that the function unsafeWithdraw calls transfer on msg.sender with- out knowing if the contract corresponding to msg.sender actually defines a fallback. Hence, the withdrawal may fail at run-time. Even worse is that the amount of Ether that sender has deposited remains indefinitely locked in Unsafe, since a deployed con- tract has no way to change itself to define a suitable fallback. Hence, Unsafe falls into the category of the greedy contracts defined by Nikolic et al. [35]. In fact, Ether sent to Unsafe from contract lacking a fallback cannot be withdrew, thus failing the afore- mentioned invariant. Such behavior is surely not malicious, but Unsafe could provide additional guarantees.
Another unsafe use of Solidity is in function unsafeCast. It is really trivial and simply attempts to retrieve an instance of a contract A using msg.sender. For the sake of simplicity, A defines just a function, f , returning the constant integer 5. The problem here is that Unsafe does not know if msg.sender will actually point to an instance of A. The documentation advices to make sure of that before going with the cast, but provides no additional or automatic ways to ensure such constraint, and nor does the compiler. Hence, if msg.sender corresponds to another contract, say B, a revert will be raised at run-time, aborting the transaction and making that sender lose money just
due to someone else’s clumsy (or not robust enough) implementation.
Consider now the snippet in Listing 7.4, showing the same Unsafe (now renamed as Safe) contract using the full power of FS+. A has remained the same and is thus omitted.
contract Saf e is Top{
mapping(addresshTopfbi ⇒ uint) balances; Saf e(mapping(addresshTopfbi ⇒ uint) balances) {
this.balances = balances; }
unit deposithTopfbi() {
return this.balances[msg.sender → this.balances[msg.sender] + msg.value]; unit }
uint saf eCasthAi() { return A(msg.sender).f (); }
unit saf eW ithdrawhTopfbi() {
return uint b = this.balances[msg.sender]; this.balances[msg.sender → 0]; msg.sender.transfer(b); unit
} }
Listing 7.4: Safe transfer and cast operations
Note the major change in the function signatures and in the declaration of balances. We impose that the key type must be addresshTopfbi. We do the same in the functions using this state variables (i.e. deposit and safeWithdraw). Similarly, we force callers of safeCastto point to an instance of A. The benefit is clear. Consider first the withdrawal of money. Remember that, due to the possible lack of the fallback, with the unsafe definition there was no chance to ensure that an amount of Ether would eventually be withdrew. This has now changed. The compiler checks that every call either to depositor safeWithdraw comes from a contract defining a fallback. On the one hand this reduces the addresses able to interact with Safe: any interaction coming from a contract lacking a fallback will be ruled out at compile-time. On the other hand, the contract’s invariant can be now proven and ensured for the entire life of an instance of Safe. The job is done by rules FUN, CALL, and CALLTOPLEVELin Section 6.3. They take the sender (either this or an explicit expression) and make sure its type is a subtype of the required one. Bearing in mind that the latter is Topfb, and looking at rules StructuralFallback in Definition 14, it is clear that such a type will define for sure a fallback: otherwise STRUCTURALFALLBACK(either 1 or 2) would not apply and the subtyping relation would not hold, thus making the compilation fail.
A similar reasoning applies to safeCast, where we impose msg.sender to refer to a subcontract of A. In this case the rules checking the subtyping is CONTRACT in Definition 14 instead of STRUCTURALFALLBACK, but the result does not change: if the subtyping does not hold the compilation fails. At run-time we will be sure that msg.sender can be safely cast to an instance of A, since it points to a contract that also is an instance of A.