티스토리 뷰
lending protocol의 기본 동작에 대해서 알아보던 중 리베이스 토큰이라는 낯선 용어를 접했다.
리베이스 토큰이란?
리베이스 토큰은 스테이블 코인과 같은 솔루션을 제공하기 위해 등장한 토큰 개념이다. 다른 자산의 가치 예를 들어 달러 등에 의존하는 것이 아닌, 공급량을 조절함으로써 가격 안정성을 유지합니다.
토큰의 유통 공급률을 조정하는 과정을 rebase mechanism이라고 하고, 공급량은 미리 정해진 수학적 공식에 따라서 정기적인 간격으로 자동으로 변화합니다.
기본적으로 리베이스 토큰에 대한 수요가 증가하면 공급도 증가하고, 수요가 적어지면 공급량도 줄어듭니다. 리베이스 토큰은 가격 탄력성을 지니기 때문에 탄력적 공급 토큰(elastic supply token)이라고도 불립니다.
리베이스 토큰은 어떻게 동작하는가?
탄력적인 공급량을 가진 리베이스 토큰은 사용자 지갑 속의 자산 가치는 변화시키지 않으면서, 토큰의 수량은 상황에 따라서 늘거나 줄 수 있습니다. 이러한 유통 공급량 조절의 자동화는 스마트 컨트랙트 위에서 동작합니다.
여기서 자산 가치를 변화시키지 않는다는 말이 꽤 헷갈렸다. 예를 들어 PIZZA토큰의 가격이 1달러고 10개를 구입했다고 치자. 그런데 가격이 상승해 개당 2달러로 오르면 나의 자산가치는 총 20달러일 것이다. 이때 리베이스 메커니즘이 작동하면 공급량을 2배로 올리고 1달러로 다시 가격을 유도할 것이다. 그럼 내 지갑에는 개당 1달러 PIZZA 토큰이 20개가 된다. 이렇게만 보면 나의 자산가치는 처음 10달러에서 20달러로 변화했다고 볼 수 있다.
하지만 여기서 설명하는 자산 가치를 변화시키지 않으면서 토큰 공급량을 늘린다는 것은 리베이스가 일어나는 그 찰나를 말한다.
- 리베이스 직전 : PIZZA 토큰 10개, 개당 2달러 → 20달러
- 리베이스 직후 : PIZZA 토큰 20개, 개당 1달러 → 20달러
직전과 직후를 비교하면 자산가치가 20달러로 동일하다. 사용자의 전체 지갑 가치는 시장 상황에 따라 계속 변하지만, 리베이스라는 수학적 처리과정이 가치를 새롭게 창출하거나 파괴하지 않음을 뜻한다. 변하지 않는 것은 토큰의 가격 하나뿐이라고 이해하면 좋을 것 같다.
리베이스 전체 메커니즘은 미리 정해진 루틴 및 기간을 바탕으로 실행됩니다. 만약 목표 가격이 1달러에 24시간으로 설정되어 있다면, 가격이 해당 목표치를 벗어나는 순간 공급량이 줄거나 늘어납니다.
리베이스 토큰을 쓰면 뭐가 좋은데?
USDT, USDC 같은 신뢰도있고 이미 시장 점유율이 높은 스테이블 코인이 있는데, 왜 굳이 리베이스 토큰이 필요할까?
가격 안정성 제공
이건 기존 스테이블 코인이 제공하고 있기 때문에 큰 장점이라고 생각이 들지 않는다.
자본 효율성
USDT 스테이블 코인은 1,000USDT를 발행하기 위해 실제 1,000달러를 담보로 두어야 합니다. 하지만 리베이스 토큰은 적은 담보로 시장 수요에 따라 공급량을 늘려 토큰을 발행할 수 있습니다.
적은 담보 혹은 무담보로 토큰을 발행하는 것은 이 토큰을 누군가가 미래에 사고 쓸 것이기 때문에, 즉 믿음의 영역에 가깝다고 생각한다. 스테이블 코인의 경우 법정화폐 달러를 담보로 갖고있다는 것에 근거하여 1달러로 페깅되지만, 리베이스 토큰은 그 조차도 없으니 스테이블 코인이 중앙집중적인 형태이더라도 리베이스 코인이 그것을 대체해야 할 필요성을 잘 느끼지 못하겠다. 하지만 아래 나오는 Lido Finance의 stETH는 스테이킹 보상을 처리할 때 리베이스 메카니즘을 사용하는데 이 부분은 적절히 사용된 것 같다.
프로토콜의 통화 주권
USDT, USDC는 Tether, Circle이라는 미국 회사가 동결할 수 있는 권한을 가집니다. 반면, 탈중앙화된 리베이스 토큰은 특정 기업에 의존하지 않고 코드에 의해 스스로 통화량을 조절합니다.
리베이스 메커니즘이 적용된 토큰
AMPL 토큰
유통 공급량은 매 24시간마다 리베이스되며, 가격을 1달러로 유지하려고 한다. AMPL 홀더들은 전체 유통량의 고정된 비율(지분)을 차지하고 있음. 리베이스로 인해 24시간마다 토큰 수는 변화하지만, 전체가치는 동일하게 유지함
stETH
stETH는 Lido에 스테이킹된 이더리움을 나타내는 리베이스형 ERC-20 토큰입니다. 일반적인 이더리움 스테이킹은 32ETH가 있어야 가능한데, Lido에서는 이보다 적은 ETH로 스테이킹 할 수 있습니다. 기본적으로 ETH를 스테이킹하면 해당 자산을 운용할 수 없는데, stETH를 받음으로써 다른 DeFi 생태계에 참여해 또 다른 수익을 낼 수 있습니다.
stETH의 총 공급량은 프로토콜에 예치된 이더리움 양에 스테이킹 보상을 더하고, validator에게 부과될 수 있는 잠재적 벌금(Penalty)을 뺀 수치를 반영합니다. stETH는 ether를 deposit할때 1:1 로 발급됩니다.
Lido 오라클이 합의 레이어의 이더리움 잔액 업데이트를 보고할 때 stETH 토큰 잔액이 매일 한국시간 오후 9시에 재계산됩니다. 이 stETH 잔고 업데이트는 리베이스 순간에 stETH를 보유한 모든 주소에서 자동으로 발생합니다. 리베이스 메커니즘은 지분 방식을 통해 구현됩니다. 구체적으로 어떻게 작동하는지 알아보겠습니다.
Lido는 개별 사용자의 잔액을 직접 수정하지 않고 지분(shares)라는 단위를 사용합니다. 지분은 프로토콜이 전체 관리하는 이더 중 내가 차지하고 있는 이더 비율을 말합니다. 내가 입금한 ETH가 전체 풀에서 차지하는 비중만큼 새로운 지분(Share)이 발행됩니다. 하루 한번 리베이스 메커니즘이 작동하면, 즉 오라클이 스테이킹 이자에 대한 보고를 하면 아래 공식에서 totalPooledEther가 변하면서 자연스럽게 사용자의 잔액이 변합니다.
balanceOf(account) = shares[account] * totalPooledEther / totalShares
.....
/**
* @dev StETH balances are dynamic and are calculated based on the accounts' shares
* and the total amount of ether controlled by the protocol. Account shares aren't
* normalized, so the contract also stores the sum of all shares to calculate
* each account's token balance which equals to:
*
* shares[account] * _getTotalPooledEther() / _getTotalShares()
*/
mapping (address => uint256) private shares;
......
/**
* @return the amount of tokens owned by the `_account`.
*
* @dev Balances are dynamic and equal the `_account`'s share in the amount of the
* total ether controlled by the protocol. See `sharesOf`.
*/
function balanceOf(address _account) external view returns (uint256) {
return getPooledEthByShares(_sharesOf(_account));
}
......
/**
* @param _sharesAmount the amount of shares to convert to ether. Must be less than UINT128_MAX.
* @return the amount of ether that corresponds to `_sharesAmount` token shares.
* @dev the result is rounded down.
*/
function getPooledEthByShares(uint256 _sharesAmount) public view returns (uint256) {
require(_sharesAmount < UINT128_MAX, "SHARES_TOO_LARGE");
return (_sharesAmount
* _getShareRateNumerator()) // numerator in ether
/ _getShareRateDenominator(); // denominator in shares
}
......
/**
* @return the numerator of the protocol's share rate (in ether).
* @dev used to convert shares to tokens and vice versa.
* @dev can be overridden in a derived contract.
*/
function _getShareRateNumerator() internal view returns (uint256) {
return _getTotalPooledEther();
}
/**
* @return the denominator of the protocol's share rate (in shares).
* @dev used to convert shares to tokens and vice versa.
* @dev can be overridden in a derived contract.
*/
function _getShareRateDenominator() internal view returns (uint256) {
return _getTotalShares();
}
+ stETH 전송 시 정밀도 손실 문제
솔리디티는 부동 소수점 타입을 지원하지 않는다. stETH를 다른 주소로 전송할때, 나의 지분에서 얼마를 차감해야하는지 계산하는 과정 즉 stETH를 지분으로 환산하는 과정에서 소수점 자리를 버려져 정밀도 손실이 발생한다. 예시를 들어서 구체적으로 설명해보겠다. 이해를 돕기 위해 이자가 극단적으로 1wei만 붙었다고 가정해보겠다.
- totalPooledEther : 101,000,000,000,000,000,001 (101 ETH + 1wei)
- totalShares: 100,000,000,000,000,000,000 (100 Shares)
- shares : 10,000,000,000,000,000,000 (10 Shares)
위 상황에서 내 잔액(stETH)는 shares * totalPooledEther / totalShares = 10,100,000,000,000,000,000.1 이다. 여기서 내 지갑에서 잔액 조회를 하면 소수점 0.1이 버려진 10.1stETH가 표시된다. 1wei가 최소 단위이므로 조회시 버려지는 0.1wei는 문제가 되지 않는다.
하지만 10.1stETH를 전부 다른 곳으로 전송하는 경우에 문제가 발생한다. 10.1stETH를 전송할 경우 차감되는 지분 계산은 다음과 같다.
shares[account] = balanceOf(account) * totalShares / totalPooledEther =
(10.1 * 10^18) * (100 * 10^18) / 101,000,000,000,000,000,001 = 9,999,999,999,999,999,999.9009...
소수점을 버리고 9,999,999,999,999,999,999 shares만 전송합니다. 이렇게 되면 전체 지분 10,000,000,000,000,000,000 - 9,999,999,999,999,999,999 = 1 즉 1shares의 가치인 1.01wei가 지갑에 남게됩니다.
지금 당장은 오차가 작아보이지만 시간이 흐를수록 이자가 쌓이고 1share당 가치가 올라가게 되면 체감하는 오차도 같이 커질 수 있습니다. 근본적인 원인은 transfer() 수행시 금액을 지분으로 바꾸는 과정에서 나눗셈 오차가 발생하는 것이므로, 나눗셈을 쓰지 않으면 정밀도 손실이 발생하지 않습니다. transferShares()를 사용하여 정밀도 손실을 없앨 수 있습니다.
function transferShares(address _recipient, uint256 _sharesAmount) external returns (uint256) {
_transferShares(msg.sender, _recipient, _sharesAmount);
uint256 tokensAmount = getPooledEthByShares(_sharesAmount);
_emitTransferEvents(msg.sender, _recipient, tokensAmount, _sharesAmount);
return tokensAmount;
}
function _transferShares(address _sender, address _recipient, uint256 _sharesAmount) internal {
require(_sender != address(0), "TRANSFER_FROM_ZERO_ADDR");
require(_recipient != address(0), "TRANSFER_TO_ZERO_ADDR");
require(_recipient != address(this), "TRANSFER_TO_STETH_CONTRACT");
_whenNotStopped();
uint256 currentSenderShares = shares[_sender];
require(_sharesAmount <= currentSenderShares, "BALANCE_EXCEEDED");
shares[_sender] = currentSenderShares.sub(_sharesAmount);
shares[_recipient] = shares[_recipient].add(_sharesAmount);
}
※ 정확하지 않은 내용이 포함될 수 있습니다.! 댓글로 알려주시면 수정하겠습니다
Rebase Token | 토큰인사이트
어떤 형식이든 상관없지만 Rebase 토큰, 또는 Elastic 토큰 또는 Elastic Supply 토큰으로도 알려진 것은 독특한 유형의 암호화폐입니다. 이러한 토큰들은 "리베이스 메커니즘"이라는 공통 요소를 공유
tokeninsight.com
Lido tokens integration guide | Lido Docs
Lido tokens integration guide | Lido Docs
This document is intended for developers looking to integrate Lido's stETH or wstETH tokens into their dApps or services, with a focus on money markets, DEXes and blockchain bridges.
docs.lido.fi
'Web3 > 온체인 금융 & DeFi' 카테고리의 다른 글
| Coinbase Tokenized Stocks, 베이스 네트워크 기반 토큰화 주식 (0) | 2026.08.31 |
|---|
- Total
- Today
- Yesterday
- storageslot
- 모빌리티 보안
- Delegatecall
- MagicNumber
- smart contract
- 토큰화주식
- 토큰전송
- rebase token
- 블록구조
- ethrnaut
- puzzlewallet
- Blockchain
- aderyn
- Ethernaut
- type2 transaction
- ethereum
- dynamic array slot
- DEFI
- ERC20
- view functions
- 이더넛
- 계정추상화
- gaslimit
- erc4337
- eip1599
- DexTwo
- oracles
- price manipulation
- solidity
- eip7702
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |