프록시 패턴이라는 말을 들으면 불변성 문제가 사라졌다고 생각하는 사람이 많습니다. 사라진 게 아니라 옮겨 갔습니다. 프록시가 상태를 들고 delegatecall로 호출을 넘기므로 구현 쪽 코드는 프록시의 스토리지 위에서 돕니다. 이후의 모든 구현은 이전의 모든 구현과 어느 슬롯에 무엇이 있는지 합의해야 합니다.
상태 변수는 선언 순서대로 배치되고, 들어갈 수 있으면 32바이트 슬롯 하나에 채워 넣으며, 상속된 변수가 먼저 옵니다. 중간에 필드를 끼우거나, 기반 컨트랙트 순서를 바꾸거나, 다른 값과 함께 채워진 uint128을 uint256으로 넓히면 새 코드는 옛 바이트를 새 의미로 읽습니다. 컴파일러는 옛 레이아웃이 있다는 사실을 모릅니다. 새 소스를 첫 배포인 것처럼 컴파일할 뿐입니다.
생성자도 도움이 안 됩니다. 생성자는 구현 자체를 배포할 때 구현의 스토리지를 대상으로 실행됩니다. 그래서 프록시는 이니셜라이저 함수에 기대고, 그 함수가 한 번만 돌도록 막아 두어야 합니다.
제 규칙은 추가만 하는 것입니다. 리뷰에서는 로직 diff를 읽기 전에 이전 버전과 새 버전의 스토리지 레이아웃부터 비교합니다. 로직 diff는 쉬운 쪽입니다.