---
# System prepended metadata

title: Gas Optimization Guidelines

---

# Gas Optimization Guidelines

## Standard Gas Optimizations

1. Use `uint256` for counters (cheaper than smaller types)
2. Pack struct variables efficiently
3. Use `calldata` for read-only array parameters
4. Cache storage variables in memory
5. Use batch operations where possible
6. Minimize storage writes
7. Use events for data that doesn't need on-chain storage

---

## PolkaVM-Specific Bytecode Size Optimizations

### Critical: Bytecode Size Limits

Every contract MUST be < 100KB compiled bytecode for PolkaVM deployment.

**Validation**:
```bash
forge build --resolc --sizes
```

### 1. Custom Errors Over String Reverts

**Impact**: ~50-100 bytes saved per error message

```solidity
// ❌ BAD - Increases bytecode
require(msg.sender == owner, "Ownable: caller is not the owner");
require(amount > 0, "Amount must be greater than zero");
require(isActive, "Contract is not active");

// ✅ GOOD - Minimal bytecode
error NotOwner();
error InvalidAmount();
error ContractNotActive();

if (msg.sender != owner) revert NotOwner();
if (amount == 0) revert InvalidAmount();
if (!isActive) revert ContractNotActive();
```

### 2. Minimize Inheritance Depth

**Impact**: 5-15KB savings depending on inherited contracts

```solidity
// ❌ BAD - Deep inheritance increases size
contract MyContract is
    ERC721Upgradeable,
    ERC721EnumerableUpgradeable,
    ERC721URIStorageUpgradeable,
    AccessControlUpgradeable,
    PausableUpgradeable,
    ReentrancyGuardUpgradeable
{ }

// ✅ GOOD - Minimal inheritance
contract MyContract is
    ERC721Upgradeable,
    AccessControlUpgradeable,
    ReentrancyGuardUpgradeable
{ }
```

**Strategy**:
- Only inherit what you absolutely need
- Implement simple features inline rather than inheriting
- Avoid extension contracts (e.g., ERC721Enumerable, ERC721URIStorage)

### 3. Avoid Heavy Libraries

**Impact**: 2-10KB savings per avoided library

```solidity
// ❌ BAD - Heavy libraries
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

// ✅ GOOD - Inline minimal implementations
// Solidity ^0.8.0 has built-in overflow checks, no SafeMath needed

// Simple counter (instead of Counters library)
uint256 private _tokenIdCounter;

function _nextTokenId() internal returns (uint256) {
    return ++_tokenIdCounter;
}

// Minimal string conversion (only if needed)
function uint2str(uint256 value) internal pure returns (string memory) {
    if (value == 0) return "0";
    uint256 temp = value;
    uint256 digits;
    while (temp != 0) {
        digits++;
        temp /= 10;
    }
    bytes memory buffer = new bytes(digits);
    while (value != 0) {
        digits -= 1;
        buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
        value /= 10;
    }
    return string(buffer);
}
```

### 4. Use Events for Historical Data

**Impact**: Variable bytecode savings + significant gas savings

```solidity
// ❌ BAD - Stores everything on-chain
struct Metadata {
    string uri;
    uint256 version;
    uint256 timestamp;
}
mapping(uint256 => Metadata[]) public metadataHistory;

function updateMetadata(uint256 tokenId, string memory uri) public {
    metadataHistory[tokenId].push(Metadata(uri, version++, block.timestamp));
}

// ✅ GOOD - Emit events, query off-chain
mapping(uint256 => string) public currentMetadata;
event MetadataUpdated(uint256 indexed tokenId, string uri, uint256 version, uint256 timestamp);

function updateMetadata(uint256 tokenId, string memory uri) public {
    currentMetadata[tokenId] = uri;
    emit MetadataUpdated(tokenId, uri, version++, block.timestamp);
}
```

### 5. Simplify Access Control

**Impact**: 3-8KB savings

```solidity
// ❌ BAD - Full AccessControl for simple needs
import "./AccessControlUpgradeable.sol";
contract MyContract is AccessControlUpgradeable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
    // Uses complex role hierarchy
}

// ✅ GOOD - Simple ownership if sufficient
import "./Ownable.sol";
contract MyContract is Ownable {
    mapping(address => bool) public minters;

    function setMinter(address account, bool status) external onlyOwner {
        minters[account] = status;
    }

    modifier onlyMinter() {
        if (!minters[msg.sender]) revert NotMinter();
        _;
    }
}
```

### 6. Remove View Functions (Query Off-Chain)

**Impact**: 1-5KB savings

```solidity
// ❌ BAD - Many on-chain view functions
function getAllTokens() public view returns (uint256[] memory) { }
function getTokensByOwner(address owner) public view returns (uint256[] memory) { }
function getTokenMetadata(uint256 tokenId) public view returns (Metadata memory) { }
function isTokenActive(uint256 tokenId) public view returns (bool) { }

// ✅ GOOD - Minimal view functions, query events off-chain
// Only keep essential view functions
function ownerOf(uint256 tokenId) public view returns (address) { }
// Query token lists and metadata from events using The Graph or similar
```

### 7. Optimize Struct Packing

**Impact**: Gas savings primarily, minor bytecode impact

```solidity
// ❌ BAD - Poor packing
struct Listing {
    uint256 price;          // 32 bytes
    address seller;         // 20 bytes
    uint256 expiresAt;      // 32 bytes
    bool isActive;          // 1 byte
    uint8 listingType;      // 1 byte
}

// ✅ GOOD - Optimal packing
struct Listing {
    address seller;         // 20 bytes
    bool isActive;          // 1 byte
    uint8 listingType;      // 1 byte
    // 10 bytes padding
    uint256 price;          // 32 bytes
    uint256 expiresAt;      // 32 bytes
}
// Saves 1 storage slot (20,000 gas per use)
```

### 8. Inline Simple Modifiers

**Impact**: 500-2000 bytes per modifier

```solidity
// ❌ BAD - Separate modifiers increase bytecode
modifier onlyActive() {
    if (!isActive) revert NotActive();
    _;
}

modifier validAmount(uint256 amount) {
    if (amount == 0) revert InvalidAmount();
    _;
}

// ✅ GOOD - Inline simple checks
function doSomething(uint256 amount) external {
    if (!isActive) revert NotActive();
    if (amount == 0) revert InvalidAmount();
    // ... function body
}
```

**Note**: Keep modifiers for complex, reused logic or security-critical checks (like `nonReentrant`, `onlyOwner`).

### 9. Batch Operations Over Loops

**Impact**: Bytecode savings + gas savings

```solidity
// ❌ BAD - Multiple functions with loops
function setMultipleURIs(uint256[] calldata tokenIds, string[] calldata uris) external {
    for (uint256 i = 0; i < tokenIds.length; i++) {
        _setTokenURI(tokenIds[i], uris[i]);
    }
}

function burnMultiple(uint256[] calldata tokenIds) external {
    for (uint256 i = 0; i < tokenIds.length; i++) {
        _burn(tokenIds[i]);
    }
}

// ✅ GOOD - Single optimized batch function
function batchOperation(
    uint256[] calldata tokenIds,
    string[] calldata uris,
    bool shouldBurn
) external {
    uint256 length = tokenIds.length;
    for (uint256 i = 0; i < length; i++) {
        if (shouldBurn) {
            _burn(tokenIds[i]);
        } else if (uris.length > i) {
            _setTokenURI(tokenIds[i], uris[i]);
        }
    }
}
```

### 10. Avoid String Concatenation

**Impact**: 2-5KB savings

```solidity
// ❌ BAD - String operations add bytecode
import "@openzeppelin/contracts/utils/Strings.sol";
string memory fullURI = string(abi.encodePacked(baseURI, Strings.toString(tokenId), ".json"));

// ✅ GOOD - Store full URIs or compute off-chain
mapping(uint256 => string) private _tokenURIs;
```

---

## Contract Size Budget Strategy

### Size Targets by Contract

| Contract | Target | Critical Threshold |
|----------|--------|-------------------|
| IPAsset | < 95KB | 98KB |
| LicenseToken | < 98KB | 99KB |
| Marketplace | < 85KB | 90KB |
| RevenueDistributor | < 80KB | 85KB |
| GovernanceArbitrator | < 75KB | 80KB |
| DeterministicFactory | < 30KB | 35KB |

### Feature Reduction Priority

When approaching size limits, remove in this order:

1. **View/Query Functions** - Implement off-chain via event indexing
2. **Administrative Convenience Functions** - Keep only essential admin functions
3. **Optional Features** - Remove nice-to-have features
4. **Complex Event Parameters** - Simplify event data
5. **Batch Operations** - Keep single operations only if desperate
6. **Last Resort** - Split contract into multiple contracts

---

## Continuous Monitoring

### During Development

```bash
# After every significant change
forge build --resolc --sizes

# Check specific contract
forge inspect ContractName bytecode --resolc | wc -c
```

### Size Alert Thresholds

- **Green**: < 80KB - Continue development
- **Yellow**: 80-90KB - Apply optimizations proactively
- **Orange**: 90-95KB - Code review for size reduction
- **Red**: 95-100KB - Critical optimization or feature removal required
- **FAIL**: > 100KB - Cannot deploy

---

## Testing Impact

**Test contracts are exempt from size limits.**

```solidity
// test/MyContract.t.sol - NO SIZE LIMIT
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";  // ✅ OK in tests
import "forge-std/Test.sol";

contract MyContractTest is Test {
    // Full testing capabilities
}
```

This allows comprehensive testing while maintaining production minimalism.

---

## References

- **PolkaVM Constraints**: `./polkavm-constraints.md`
- **Implementation Guide**: `/docs/IMPLEMENTATION_GUIDE.md`
- **Foundry-Polkadot**: https://github.com/paritytech/foundry-polkadot

---
