TRON Foundation
Jun 29 · 4 min read

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 https://troneye.com (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: https://troneye.com/reveal?address=TKPHQ4e3N5BDUhVsHcFygHcmrhMWRBRL2y) and Logger (address: https://troneye.com/reveal?address=TPXqqKRNV6V4Fw7A7daEz2xXE8k2vi7FDK).

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;

14.

15. constructor() public {

16. lastPlayed = now;

17. shuffle();

18. }

19.

20. function play(uint256 number_) payable public {

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

22. lastPlayed = now;

23.

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

25.

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

27.

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

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

30. }

31. lastBet = msg.value;

32.

33. shuffle();

34. }

35.

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 {

2.

3. uint256 public time = now;

4. uint256 public lastPlayed;

5. address public player;

6.

7.

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

9. player = msg.sender;

10. }

11.

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 https://troneye.com.

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

TRON

TRON

TRON Foundation

Written by

The official medium of Tron Foundation

TRON

TRON

TRON

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade