Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

Three variable that are in `LevelOne` but not included in `LevelTwo` leads to storage collision

Description: Variables LevelOne:schoolFees, LevelOne:reviewCount and LevelOne:lastReviewTime at storage slot 2, 9 & 10 respectively, are not included in LevelTwo contract variable which cause mixing up of variable.

Vulnerability Details: four variables that are in LevelOne contract but not included in LevelTwo contract

  • LevelTwo contract

contract LevelTwo is Initializable{
using SafeERC20 for IERC20;
address principal;
bool inSession;
@> // missing variable
uint256 public sessionEnd;
uint256 public bursary;
uint256 public cutOffScore;
mapping(address => bool) public isTeacher;
mapping(address => bool) public isStudent;
mapping(address => uint256) public studentScore;
@> // missing variable
@> // missing variable
address[] listOfStudents;
address[] listOfTeachers;
uint256 public constant TEACHER_WAGE_L2 = 40;
uint256 public constant PRINCIPAL_WAGE_L2 = 5;
uint256 public constant PRECISION = 100;
IERC20 usdc;
  • LevelOne contract

contract LevelOne is Initializable, UUPSUpgradeable {
using SafeERC20 for IERC20;
////////////////////////////////
///// /////
///// VARIABLES /////
///// /////
////////////////////////////////
address principal;
bool inSession;
@> uint256 schoolFees;
@> uint256 public immutable reviewTime = 1 weeks;
uint256 public sessionEnd;
uint256 public bursary;
uint256 public cutOffScore;
mapping(address => bool) public isTeacher;
mapping(address => bool) public isStudent;
mapping(address => uint256) public studentScore;
@> mapping(address => uint256) private reviewCount;
@> mapping(address => uint256) private lastReviewTime;
address[] listOfStudents;
address[] listOfTeachers;
uint256 public constant TEACHER_WAGE = 35; // 35%
uint256 public constant PRINCIPAL_WAGE = 5; // 5%
uint256 public constant PRECISION = 100;
IERC20 usdc;

Impact: storage collision

Tools Used: Manual Review

Proof of Concept: For this to work add UUPSUpgradable to LevelTwo contract

Add this test to your LeveOnelAndGraduateTest.t.sol

  1. First We add teachers and enroll students set value for bursary at slot 5

  2. Upgrade the protocol to level two

  3. storage slot of bursary is at 3

  4. when we try to get bursary at slot 3 we actually get value of reviewTime of value 1 weeks at slot3 in LevelOne contract

Proof of Code
function test_StorageCollision() public {
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
levelOneProxy.addTeacher(bob);
vm.stopPrank();
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(dan);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(eli);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(fin);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(grey);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(harriet);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
uint256 cuttOffScroe = 70;
vm.prank(principal);
levelOneProxy.startSession(cuttOffScroe);
bool isSessionOver = block.timestamp > levelOneProxy.getSessionEnd();
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
uint256 sessionEndOfLevelOne = levelOneProxy.sessionEnd();
vm.startPrank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
levelOneProxy.upgradeToAndCall(levelTwoImplementationAddress, data);
vm.stopPrank();
uint256 bursaryOfLevelTwo = LevelTwo(proxyAddress).bursary();
assert(usdc.balanceOf(principal) == 1500 ether);
assert(LevelTwo(proxyAddress).TEACHER_WAGE_L2() == 40);
assert(!isSessionOver);
// storage collision
assert(bursaryOfLevelTwo == sessionEndOfLevelOne);
}

Recommendations: Add the missing variables that were not included in LevelTwo contract

contract LevelTwo is Initializable, UUPSUpgradeable {
using SafeERC20 for IERC20;
address principal;
bool inSession;
+ uint256 schoolFees;
uint256 public sessionEnd;
uint256 public bursary;
uint256 public cutOffScore;
mapping(address => bool) public isTeacher;
mapping(address => bool) public isStudent;
mapping(address => uint256) public studentScore;
+ mapping(address => uint256) private reviewCount;
+ mapping(address => uint256) private lastReviewTime;
address[] listOfStudents;
address[] listOfTeachers;
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

storage collision

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

storage collision

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.