Skip to content

Commit

Permalink
feat: Add storage safety for UUPS upgradeable contracts
Browse files Browse the repository at this point in the history
- Add storage gaps to prevent storage collision in future upgrades
- Document storage layout for all storage contracts
- Clarify inheritance order for upgradeable contracts
- Add upgrade validation functions in deployment scripts

Storage contracts modified:
- LoyaltyBridgeStorage
- LoyaltyBurnerStorage
- LoyaltyConsumerStorage
- LoyaltyExchangerStorage
- LoyaltyProviderStorage
- LoyaltyTransferStorage
- PhoneStorage
- ShopStorage
- ValidatorStorage
- CurrencyStorage

This change ensures safe upgrades by:
1. Reserving storage slots with gaps
2. Documenting storage layout
3. Adding storage layout validation
4. Improving deployment scripts for upgrades
  • Loading branch information
MichaelKim20 committed Dec 28, 2024
1 parent 096022d commit d92e269
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 7 deletions.
18 changes: 18 additions & 0 deletions packages/contracts/contracts/controllers/LoyaltyBridgeStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ import "acc-bridge-contracts-v2/contracts/interfaces/IBridgeValidator.sol";

import "../interfaces/ILedger.sol";

/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - deposits: mapping(bytes32 => DepositData) at slot 0
* - withdraws: mapping(bytes32 => WithdrawData) at slot 1
* - confirmations: mapping(bytes32 => mapping(address => bool)) at slot 2
* - systemAccount: address at slot 3
* - protocolFee: uint256 at slot 4
* - isSetLedger: bool at slot 5
* - ledgerContract: ILedger at slot 6
* - validatorContract: IBridgeValidator at slot 7
* - tokenContract: BIP20DelegatedTransfer at slot 8
* - tokenId: bytes32 at slot 9
* - __gap: uint256[50] starting at slot 10
*/
contract LoyaltyBridgeStorage {
mapping(bytes32 => IBridge.DepositData) internal deposits;
mapping(bytes32 => IBridge.WithdrawData) internal withdraws;
Expand All @@ -23,4 +39,6 @@ contract LoyaltyBridgeStorage {
IBridgeValidator internal validatorContract;
BIP20DelegatedTransfer internal tokenContract;
bytes32 internal tokenId;

uint256[50] private __gap;
}
12 changes: 12 additions & 0 deletions packages/contracts/contracts/controllers/LoyaltyBurnerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,22 @@ import "../interfaces/IPhoneLinkCollection.sol";
import "../interfaces/IValidator.sol";
import "../interfaces/ILedger.sol";

/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - validatorContract: IValidator at slot 0
* - linkContract: IPhoneLinkCollection at slot 1
* - ledgerContract: ILedger at slot 2
* - isSetLedger: bool at slot 3
* - __gap: uint256[50] starting at slot 4
*/
contract LoyaltyBurnerStorage {
IValidator internal validatorContract;
IPhoneLinkCollection internal linkContract;
ILedger internal ledgerContract;

bool internal isSetLedger;

uint256[50] private __gap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ import "../interfaces/ICurrencyRate.sol";
import "../interfaces/IShop.sol";
import "../interfaces/ILedger.sol";

/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - loyaltyPayments: mapping(bytes32 => LoyaltyPaymentData) at slot 0
* - systemAccount: address at slot 1
* - temporaryAddress: address at slot 2
* - currencyRateContract: ICurrencyRate at slot 3
* - shopContract: IShop at slot 4
* - ledgerContract: ILedger at slot 5
* - isSetLedger: bool at slot 6
* - isSetShop: bool at slot 7
* - __gap: uint256[50] starting at slot 8
*/
contract LoyaltyConsumerStorage {
enum LoyaltyPaymentStatus {
INVALID,
Expand Down Expand Up @@ -35,6 +49,7 @@ contract LoyaltyConsumerStorage {
LoyaltyPaymentStatus status;
}

mapping(bytes32 => LoyaltyPaymentData) internal loyaltyPayments;
address internal systemAccount;
address internal temporaryAddress;

Expand All @@ -45,5 +60,5 @@ contract LoyaltyConsumerStorage {
bool internal isSetLedger;
bool internal isSetShop;

mapping(bytes32 => LoyaltyPaymentData) internal loyaltyPayments;
uint256[50] private __gap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@
pragma solidity ^0.8.2;

import "../interfaces/IPhoneLinkCollection.sol";

import "../interfaces/ICurrencyRate.sol";
import "../interfaces/ILedger.sol";

/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - systemAccount: address at slot 0
* - linkContract: IPhoneLinkCollection at slot 1
* - currencyRateContract: ICurrencyRate at slot 2
* - ledgerContract: ILedger at slot 3
* - isSetLedger: bool at slot 4
* - __gap: uint256[50] starting at slot 5
*/
contract LoyaltyExchangerStorage {
address internal systemAccount;

Expand All @@ -15,4 +25,6 @@ contract LoyaltyExchangerStorage {
ILedger internal ledgerContract;

bool internal isSetLedger;

uint256[50] private __gap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,50 @@
pragma solidity ^0.8.2;

import "../interfaces/IPhoneLinkCollection.sol";

import "../interfaces/ICurrencyRate.sol";
import "../interfaces/IValidator.sol";
import "../interfaces/IShop.sol";
import "../interfaces/ILedger.sol";

/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - purchases: mapping(string => bool) at slot 0
* - validatorContract: IValidator at slot 1
* - linkContract: IPhoneLinkCollection at slot 2
* - currencyRateContract: ICurrencyRate at slot 3
* - shopContract: IShop at slot 4
* - ledgerContract: ILedger at slot 5
* - systemAccount: address at slot 6
* - isSetLedger: bool at slot 7
* - isSetShop: bool at slot 8
* - adActionAgentFee: uint32 at slot 9
* - adActionProtocolFee: uint32 at slot 10
* - adActionProtocolFeeAccount: address at slot 11
* - __gap: uint256[50] starting at slot 12
*/
contract LoyaltyProviderStorage {
uint32 public constant DEFAULT_AD_ACTION_AGENT_FEE = 200;
uint32 public constant MAX_AD_ACTION_AGENT_FEE = 500;
uint32 public constant DEFAULT_AD_ACTION_PROTOCOL_FEE = 300;
uint32 public constant MAX_AD_ACTION_PROTOCOL_FEE = 500;

mapping(string => bool) internal purchases;

IValidator internal validatorContract;
IPhoneLinkCollection internal linkContract;
ICurrencyRate internal currencyRateContract;
IShop internal shopContract;
ILedger internal ledgerContract;
address internal systemAccount;

mapping(string => bool) internal purchases;

bool internal isSetLedger;
bool internal isSetShop;

uint32 internal adActionAgentFee;
uint32 internal adActionProtocolFee;
address internal adActionProtocolFeeAccount;

uint256[50] private __gap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@ pragma solidity ^0.8.2;

import "../interfaces/ILedger.sol";

/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - systemAccount: address at slot 0
* - protocolFee: uint256 at slot 1
* - ledgerContract: ILedger at slot 2
* - isSetLedger: bool at slot 3
* - __gap: uint256[50] starting at slot 4
*/
contract LoyaltyTransferStorage {
address internal systemAccount;
uint256 internal protocolFee;

ILedger internal ledgerContract;

bool internal isSetLedger;

uint256[50] private __gap;
}
15 changes: 14 additions & 1 deletion packages/contracts/contracts/currency/CurrencyStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ pragma solidity ^0.8.2;

import "../interfaces/IValidator.sol";

/// @notice 토큰 가격을 제공하는 스마트컨트랙트
/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - NULL_CURRENCY: bytes32 at slot 0
* - MULTIPLE: uint256 at slot 1
* - rates: mapping(string => uint256) at slot 2
* - prevHeight: uint256 at slot 3
* - validator: IValidator at slot 4
* - tokenSymbol: string at slot 5
* - __gap: uint256[50] starting at slot 6
*/
contract CurrencyStorage {
bytes32 public constant NULL_CURRENCY = keccak256(abi.encodePacked(""));
uint256 public constant MULTIPLE = 1000000000;
Expand All @@ -14,4 +25,6 @@ contract CurrencyStorage {

IValidator internal validator;
string internal tokenSymbol;

uint256[50] private __gap;
}
2 changes: 2 additions & 0 deletions packages/contracts/contracts/ledger/LedgerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ contract LedgerStorage {
IBIP20DelegatedTransfer internal tokenContract;
ICurrencyRate internal currencyRateContract;
bytes32 internal tokenId;

uint256[50] private __gap;
}
17 changes: 17 additions & 0 deletions packages/contracts/contracts/phone/PhoneStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

pragma solidity ^0.8.2;

/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - NULL: bytes32 at slot 0
* - phoneToAddress: mapping(bytes32 => address) at slot 1
* - addressToPhone: mapping(address => bytes32) at slot 2
* - nonce: mapping(address => uint256) at slot 3
* - requests: mapping(bytes32 => RequestItem) at slot 4
* - requestIds: bytes32[] at slot 5
* - quorum: uint256 at slot 6
* - validators: mapping(address => ValidatorItem) at slot 7
* - validatorAddresses: address[] at slot 8
* - __gap: uint256[50] starting at slot 9
*/
contract PhoneStorage {
/// @notice 요청 아이템의 상태코드
enum RequestStatus {
Expand Down Expand Up @@ -43,4 +58,6 @@ contract PhoneStorage {
uint256 internal quorum;
mapping(address => ValidatorItem) internal validators;
address[] internal validatorAddresses;

uint256[50] private __gap;
}
19 changes: 18 additions & 1 deletion packages/contracts/contracts/shop/ShopStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,22 @@ import "../interfaces/ICurrencyRate.sol";
import "../interfaces/IShop.sol";
import "../interfaces/ILedger.sol";

/// @notice 상점컬랙션
/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - shops: mapping(bytes32 => ShopData) at slot 0
* - shopIdByAddress: mapping(address => bytes32[]) at slot 1
* - settlements: mapping(bytes32 => ShopSettlementData) at slot 2
* - items: bytes32[] at slot 3
* - providerAddress: address at slot 4
* - consumerAddress: address at slot 5
* - nonce: mapping(address => uint256) at slot 6
* - currencyRate: ICurrencyRate at slot 7
* - ledgerContract: ILedger at slot 8
* - isSetLedger: bool at slot 9
* - __gap: uint256[50] starting at slot 10
*/
contract ShopStorage {
mapping(bytes32 => IShop.ShopData) internal shops;
mapping(address => bytes32[]) internal shopIdByAddress;
Expand All @@ -22,4 +37,6 @@ contract ShopStorage {
ILedger internal ledgerContract;

bool internal isSetLedger;

uint256[50] private __gap;
}
13 changes: 13 additions & 0 deletions packages/contracts/contracts/validator/ValidatorStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "../interfaces/IValidator.sol";

/**
* @dev 스토리지 레이아웃
*
* [스토리지 슬롯 레이아웃]
* - MINIMUM_DEPOSIT_AMOUNT: uint256 at slot 0
* - token: IERC20 at slot 1
* - items: address[] at slot 2
* - activeItems: address[] at slot 3
* - validators: mapping(address => ValidatorData) at slot 4
* - __gap: uint256[50] starting at slot 5
*/
contract ValidatorStorage {
uint256 public constant MINIMUM_DEPOSIT_AMOUNT = 100000 ether;
IERC20 internal token;
address[] internal items;
address[] internal activeItems;
mapping(address => IValidator.ValidatorData) internal validators;

uint256[50] private __gap;
}

0 comments on commit d92e269

Please sign in to comment.