Published in


Smart Contract Security Class (№007)

In this class, we will focus on the dangerous delegatecall function. At the same time, everyone is welcome to follow TRON official twitter, and submit your contract code thought TRON-Eye.

The following is the contract code from (hereinafter referred to as TRON-Eye). TRON-Eye is a TRON verification platform from the community. The previous classes have introduced the TRON-Eye verification platform in detail. This contract is RouletteV5 (address: and Logger (address:

Figure 1 RouletteV5’s source code

The code for the RouletteV5 contract is very short, the core code is as follows.

1. contract RouletteV5 {

2. address public owner = msg.sender;

3. uint256 public lastPlayed;

4. uint256 public secretNumber;

5. uint256 public lastBet;

6. uint256 public betPrice = 1000;

7. struct Game {

8. uint256 number;

9. uint256 secret;

10. address player;

11. }

12. address public logger;

13. Game[] public gamesPlayed;


15. constructor() public {

16. lastPlayed = now;

17. shuffle();

18. }


20. function play(uint256 number_) payable public {

21. require(msg.value >= betPrice && number_ <= 10);

22. lastPlayed = now;


24. logger.delegatecall(bytes4(keccak256(“logPlay(uint256,uint256)”)), secretNumber, number_);


26. gamesPlayed.push(Game(number_, secretNumber, msg.sender));


28. if (number_ == secretNumber) { // win!

29. msg.sender.transfer(address(this).balance);

30. }

31. lastBet = msg.value;


33. shuffle();

34. }


36. function kill() public {

37. logger.delegatecall(bytes4(keccak256(“logKill()”)));

38. require(owner == msg.sender && now > lastPlayed + 1 days);

39. selfdestruct(msg.sender);

40. }

This time it is still a roulette contract. When the contract is initialized, a random number of 1–20 is set: secretNumber, the player guesses the number by play() and updates the secretNumber. For the sake of demonstration, the contract’s secretNumber is set to public, and multiple query functions are provided.

However, with the delegatecall on line 24, the player will never be able to win through play(). How did this happen?

We all know that delegatecall is to execute the code of the called contract in the data space of the current contract. The following is the core code of the called contract, which is why many developers feel that delegatecall is very dangerous. Here is the called Logger contract’s key code.

1. contract Logger {


3. uint256 public time = now;

4. uint256 public lastPlayed;

5. address public player;



8. function logPlay(uint256 secretNumber, uint256 number) payable public {

9. player = msg.sender;

10. }


12. function logKill() public {

13. lastPlayed = 0;

14. }

15. }

You can see that logger.delegatecall(bytes4(keccak256(“logPlay(uint256,uint256)”)), secretNumber, number_); is calling the logPlay() method of the Logger contract. In the 9th line of the Logger, the player of the Logger contract is modified. So what effect does it have on the outer call contract, causing RouletteV5’s play() to never guess the secretNumber?

This needs to talk about how Solidity’s member variables are stored.

Static-sized variables (all types except map mapping and dynamic arrays) are placed consecutively in storage from position 0. In addition, if possible, multiple variables with less than 32 bytes of storage will be packaged into one storage slot.

In other words, for solidity, the storage location of the variable has nothing to do with the name of the variable, only its position in the variable list.

So the Logger’s player variable corresponds to the 3th variable secretNumber in RouletteV5. And the storage length of uint256 and address are both 32 bytes. So when RouletteV5 calls the 9th line of the Logger via delegatecall, it overrides the secretNumber of RouletteV5 to a uint256(msg.sender) whose value is, in general, very large. Then you can never guess.

In line 37 of Roulette V5, in the same way, the lastPlayed is changed to 0 by the 13 lines of the Logger, so the require(owner == msg.sender && now > lastPlayed + 1 days) is passed.

Finally, you may have noticed two functions of the Logger, one with a payable flag and one without. Then:

1. How does the payable flag affect the corresponding bytecode?

2. Will it affect the corresponding delegatecall?

3. Why does payable have this effect?

Welcome to follow our twitter and tell me the answer.

This class will be end here. Thanks to TRON-Eye for providing the contract verification tool. More information about them can be found directly on their website

We continue to call for source code for follow-up classes, and we welcome your official twitter submissions.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


The official Medium of Tron Foundation. Learn more: