
巧みなコントラクト設計、stETHはどのようにして毎日自動で収益を分配しているのか?
글: ZAN 팀
필자는 소량의 ETH를 stETH로 교환한 후, stETH가 매일 자연스럽게 증가하며 수익을 지속적으로 얻는 것을 발견했다. 하지만 내 계정에서 어떤 거래 기록도 확인할 수 없었다. 그 이유는 무엇일까? 본문에서는 이 독특한 설계 뒤에 숨은 원리를 살펴보고, 수익이 지급되는 비밀을 밝혀보려 한다.

1개의 stETH가 며칠 후 이미 일부 수익을 얻고 있음
그 전에 먼저 stETH가 수익을 얻는 구조적 원리인 이더리움 스테이킹(Staking)에 대해 간략히 설명하고자 한다. 해당 개념을 이미 이해한 독자는 아래로 바로 넘어가도 좋다.

초기 이더리움은 비트코인과 마찬가지로 작업증명(PoW, Proof of Work) 방식을 합의 메커니즘으로 사용했지만, 전력 소비 및 보안성·성능 측면에서의 문제들로 인해 2022년 9월부터 지분증명(PoS, Proof of Stake) 방식으로 전환되었다.
예전에는 채굴 장비의 연산 능력을 통해 합의를 이루던 이더리움은 이제 32개 이상의 ETH를 스테이킹하여 투표권을 얻고, 이를 통해 수익을 얻는 방식으로 바뀌었으며, PoS 방식으로 합의를 달성하도록 유도하고 있다.
32개의 ETH를 스테이킹하면 이더리움 네트워크에 참여하여 검증자(Validator)가 되어 데이터 저장, 거래 처리 및 블록체인에 새로운 블록 추가 등의 역할을 수행할 수 있다. 새 블록을 올바르게 생성하고 다른 검증자의 작업을 검증하는 것만으로도 ETH 보상을 받을 수 있으므로, 이는 ETH를 스테이킹함으로써 비교적 안정적인 수익을 얻을 수 있는 방법이라고 볼 수 있다.
하지만 이러한 스테이킹 방식은 일반 사용자에게 여전히 진입장벽이 높다. 우선 32개의 ETH와 365일 24시간 이더리움 네트워크에 연결된 전용 컴퓨터가 필요하기 때문이다. 또한 스테이킹한 ETH는 유동성이 사라진다. 이런 문제를 해결하기 위해 등장한 것이 유동성 스테이킹 파생상품(Liquid Staking Derivatives, LSD)이다. LSD는 기존 스테이킹의 높은 진입 장벽과 유동성 부족 문제를 해결하며, 사용자가 32개 미만의 ETH로도 스테이킹에 참여할 수 있고, 직접 노드를 운영하지 않아도 제3자에게 ETH를 위탁하여 스테이킹할 수 있도록 한다. 대신 Lido의 stETH나 Rocket Pool의 rETH 같은 스테이킹 토큰을 발행받는데, 이 유동성 토큰들은 다른 플랫폼에서 거래하거나 담보로 활용하거나 기타 금융 활동에 사용할 수 있다. 따라서 사용자는 보상을 받으며 스테이킹에 쉽게 참여하면서도 자금의 유연성을 유지할 수 있게 된다.

결국 stETH의 핵심 로직은 자신의 ETH를 Lido에 맡기면, Lido가 이를 이용해 이더리움 PoS에 참여하여 수익을 얻고, 사용자는 그에 상응하는 stETH를 증거로 받는 것이다. 이후 Lido는 stETH 보유 주소들에게 수익을 분배하게 된다.
stETH의 수익은 매일 자동으로 업데이트되는 것을 확인할 수 있으며, 아래는 테스트를 통해 확인한 수익 변화이며, 매일 암호화 지갑에서 관련 내용을 확인할 수 있다.

여기서 스마트 컨트랙트 개발에 익숙한 독자라면 의문을 가질 수 있다. 매일 지급되는 수익이 매우 적은데, 수익보다 가스비가 더 클 수도 있지 않을까?
사실 그렇다. 만약 Lido가 가장 단순한 방식으로 수익을 분배한다면, 가스비를 감당하기 어려울 것이다. 수많은 주소에 토큰을 직접 송금하는 것은 가스 비용 측면에서 상상할 수 없이 큰 부담이다.
그럼에도 불구하고 실제로 Lido는 지갑에 있는 stETH 수량이 자동으로 증가하게 만들었고, 우리는 해당 주소에서 어떠한 거래 기록도 찾을 수 없다. 어떻게 가능한 일일까?
Lido 컨트랙트 https://etherscan.io/token/0xae7ab96520de3a18e5e111b5eaab095312d7fe84 에 접속하여 balanceOf 함수를 추적해보자.

balanceOf는 ERC20 표준에 부합하는 함수이며, 지갑 앱은 이 함수를 호출하여 사용자의 토큰 잔액을 조회한다.
stETH 컨트랙트의 balanceOf 함수는 getPooledEthByShares 함수를 호출하고 있다. 이 함수는 mapping(address => uint256) private shares; 변수를 입력값으로 받는다. 즉, 각 주소가 얼마나 많은 stETH를 보유하고 있는지를 나타내는 값인데, 이것이 곧 stETH 수량일까? 그렇지 않다. 만약 매일 모든 주소의 데이터를 업데이트해야 한다면, 이 또한 커다란 가스 비용을 초래할 것이다. 물론 한 번의 트랜잭션으로 전체 주소의 shares 값을 갱신하는 것도 가능하겠지만, 여전히 막대한 가스 비용이 발생한다.
여기까지 오면 독자들은 아마 컨트랙트가 어떻게 작동하는지 감을 잡았을 것이다. 이제 getPooledEthByShares 함수를 살펴보자.

최종 반환값은 sharesAmount × _getTotalPooledEther() ÷ _getTotalShares로 계산된다.
_getTotalPooledEther는 stETH의 총량(1:1 환산 기준으로 ETH 총량도 의미함), _getTotalShares는 전체 지분 수를 의미한다. 즉, 각 주소의 stETH 수량은 동적으로 계산되는 것이다.
예를 들어 현재 전체 지분(_getTotalShares)이 1000이고, A 주소가 100의 지분(sharesAmount)을 가지고 있다고 하자. 이 1000 지분은 1000개의 stETH(_getTotalPooledEther)에 해당한다고 하면, A 주소는 100개의 stETH를 보유한 것으로 계산된다. 이후 Lido가 이 1000개의 ETH를 스테이킹해 1개의 ETH 수익을 얻으면, _getTotalPooledEther는 1001로 업데이트된다. 즉, 기존 1000개였던 stETH가 1001개로 늘어난 것이다. 그러면 A 주소의 stETH는 100 × 1001 / 1000 = 100.1개가 된다.
간단히 말해, 각 주소의 지분 수는 변하지 않지만, 지분이 포함된 stETH 총량이 늘어나므로 결과적으로 stETH 수량이 자연스럽게 증가하는 것이다.
코드를 계속 살펴보면, _getTotalPooledEther의 값은 handleOracleReport 함수의 영향을 받으며, 이 함수가 컨트랙트 내 관련 데이터를 업데이트한다. 실제 호출은 https://etherscan.io/address/0x852deD011285fe67063a08005c71a85690503Cee 주소의 컨트랙트가 정기적으로 submitReportData를 호출함으로써 이루어진다.(submitReportData 내부에서 Lido 컨트랙트의 handleOracleReport를 호출)

매일 데이터가 업데이트되는 것을 확인할 수 있는데, 이것이 바로 우리 주소에 수익 지급 거래 기록이 없음에도 불구하고 잔액이 매일 변하는 이유다.
이러한 설계는 이더리움 ERC20 스마트 컨트랙트의 특징을 보여준다. ERC20 토큰의 잔고는 주소에 '고정되어' 기록되는 것이 아니라, 컨트랙트 함수가 반환하는 값이라는 점이다. 따라서 아무런 거래 기록이 없더라도 토큰 수량이 변할 수 있으며, 이는 ERC20 컨트랙트의 유연성을 높이지만, 반면에 컨트랙트에 익숙하지 않은 사용자들에게 혼란을 줄 수 있다. 본문이 스마트 컨트랙트를 더 잘 이해하고, 보다 안전하게 상호작용하는 데 도움이 되기를 바란다.
또한 ETH를 stETH로 스테이킹하면 안정적인 수익을 얻는 것처럼 보일 수 있지만, 여전히 리스크가 존재할 수 있다. 본문은 스테이킹 컨트랙트에 대한 기술적 연구 참고용일 뿐이며, 어떤 투자 권유도 포함하지 않는다.
TechFlow 공식 커뮤니티에 오신 것을 환영합니다
Telegram 구독 그룹:https://t.me/TechFlowDaily
트위터 공식 계정:https://x.com/TechFlowPost
트위터 영어 계정:https://x.com/BlockFlow_News














