ERC-7837 확산형 토큰 (전송 시 발행) (Diffusive Tokens)
Final · 토큰 · 2024-12-07 작성
토큰을 전송해도 보낸 사람의 잔액이 줄지 않고 받는 사람에게 새 토큰이 발행되는 대체가능 토큰 표준입니다. 전송마다 토큰당 수수료를 ETH 같은 기본 통화로 내고 전체 발행량에는 상한이 있습니다.
왜 필요한가
기존 ERC-20 토큰은 전송할 때 보낸 사람의 잔액을 줄이고 받는 사람의 잔액을 늘리는, 정해진 공급량을 나눠 갖는 구조입니다. 그런데 특정 상품 묶음에 대한 교환권처럼 토큰이 퍼져 나가는 과정 자체가 공급을 늘려야 자연스러운 경우가 있습니다. 기존 표준으로는 전송할 때마다 새 토큰을 발행하는 동작을 표현할 방법이 없고 그런 컨트랙트를 각자 다르게 만들면 지갑이나 거래소가 동작을 예측하기 어렵습니다. 또 전송만 반복하면 토큰이 무한히 생기는 문제를 막을 공통 장치도 필요합니다. 이 표준은 전송 시 발행, 토큰당 고정 수수료, 최대 발행량 상한, 소각이라는 규칙을 하나로 묶어 이런 확산형(diffusive) 토큰을 일관된 방식으로 만들 수 있게 합니다.
구현해야 하는 것
function balanceOf(address account) external view returns (uint256);: 해당 주소가 가진 토큰 잔액을 돌려줍니다. ERC-20과 동일하게 동작합니다.function transfer(address to, uint256 amount) external payable returns (bool);: to에게 amount만큼 새 토큰을 발행합니다. 보낸 사람의 잔액은 줄지 않으며 msg.value로 transferFee * amount 이상의 수수료를 함께 보내야 합니다.function burn(uint256 amount) external;: 호출자의 잔액과 totalSupply를 amount만큼 줄입니다. 실물 상환이나 공급 축소에 씁니다.function setMaxSupply(uint256 newMax) external;: 소유자만 호출할 수 있으며 최대 발행량을 바꿉니다.function setTransferFee(uint256 newFee) external;: 소유자만 호출할 수 있으며 토큰당 전송 수수료를 바꿉니다.function withdrawFees(address payable recipient) external;: 소유자만 호출할 수 있으며 컨트랙트에 쌓인 기본 통화 수수료를 recipient에게 보냅니다.function approve(address spender, uint256 amount) external returns (bool);: spender가 호출자 몫으로 amount까지 전송을 대행할 수 있게 승인합니다. ERC-20 호환용입니다.function transferFrom(address from, address to, uint256 amount) external payable returns (bool);: 승인 한도(allowance)를 차감하면서 to에게 새 토큰을 발행합니다. from의 잔액은 줄지 않습니다.event Transfer(address indexed from, address indexed to, uint256 amount);: 전송 호출로 to에게 토큰이 발행될 때 발생합니다.event Burn(address indexed burner, uint256 amount);: 어떤 주소에서 amount만큼 토큰이 소각될 때 발생합니다.event FeeUpdated(uint256 newFee);: 소유자가 전송 수수료를 바꿀 때 발생합니다.event MaxSupplyUpdated(uint256 newMaxSupply);: 소유자가 최대 발행량을 바꿀 때 발생합니다.
심층 설명
전송은 잔액 이동이 아니라 발행입니다
A가 B에게 amount만큼 전송하면 A의 잔액은 그대로이고 B의 잔액과 totalSupply가 amount만큼 늘어납니다. totalSupply + amount가 maxSupply를 넘으면 반드시 되돌려집니다(revert). 참조 구현은 to가 0 주소이거나 amount가 0이면 실패하게 합니다. Transfer 이벤트는 토큰이 실제로 이동한 게 아니라 전송 호출로 to에게 새로 발행됐을 때 발생하므로, 이벤트만 보고 from의 잔액이 줄었다고 가정하면 안 됩니다.
토큰당 고정 수수료는 기본 통화로 냅니다
보낸 사람은 전송할 때 transferFee * amount만큼의 기본 통화(ETH, wei 단위)를 msg.value로 함께 보내야 하고 부족하면 거래가 되돌려집니다. 이 수수료는 컨트랙트에 쌓였다가 소유자가 withdrawFees로 회수합니다. 전송할 때마다 실제 비용이 들기 때문에 전송을 반복해 무한히 발행하는 행동이 경제적으로 억제되고 동시에 발행자의 수익원이 됩니다.
최대 발행량과 소각이 공급을 통제합니다
maxSupply는 totalSupply가 도달할 수 있는 상한이며 이를 넘기는 전송은 모두 실패합니다. 보유자는 burn(amount)으로 자기 잔액과 totalSupply를 함께 줄일 수 있고 이때 Burn 이벤트가 발생합니다. 소각은 토큰을 실물 상품이나 서비스로 교환(상환)하는 동작에 대응시키거나 단순히 공급을 줄이는 데 쓸 수 있습니다. 참조 구현의 setMaxSupply는 새 상한이 현재 totalSupply 이상이어야 한다는 조건을 둡니다.
approve와 transferFrom은 호환용이며 의미가 다릅니다
approve와 transferFrom은 ERC-20 도구와의 상호운용을 위해 남아 있지만 논리가 다릅니다. transferFrom은 from이 msg.sender에게 준 allowance를 amount만큼 차감하되, from의 잔액은 전혀 줄이지 않고 to에게 새 토큰을 발행합니다. 수수료 transferFee * amount는 transferFrom을 호출한 쪽이 msg.value로 냅니다. ERC-20에서 기대하는 'from 잔액 차감' 동작이 적용되지 않으므로 이를 전제로 짠 통합 코드는 그대로 쓸 수 없습니다.
소유자가 수수료와 상한을 관리합니다
소유자(owner)는 setTransferFee로 토큰당 수수료를, setMaxSupply로 최대 발행량을 갱신할 수 있고 각각 FeeUpdated, MaxSupplyUpdated 이벤트가 발생합니다. 쌓인 수수료는 withdrawFees(recipient)로 회수하며, 참조 구현은 컨트랙트의 전체 잔액을 recipient에게 보냅니다. 발행자가 시장 상황에 따라 유연하게 조건을 조정할 수 있게 한 설계이지만 그만큼 소유자 권한이 토큰 경제에 직접 영향을 줍니다.
ERC-20과는 인터페이스만 호환됩니다
balanceOf, transfer, approve, transferFrom 같은 함수 구성은 ERC-20과 같아서 대부분의 ERC-20 호환 도구가 잔액을 표시하고 전송을 시작할 수 있습니다. 하지만 전송이 재분배가 아니라 발행이라는 점에서 동작은 크게 다르며 원문도 이런 특이한 경제 구조가 사용자와 가격 책정 메커니즘에 혼동을 줄 수 있다고 명시합니다. DIFF 토큰을 통합하는 시스템은 반드시 이 차이를 이해하고 있어야 합니다.
쓸 때 주의할 점
- 재진입 공격 방지: 수수료 등 기본 통화를 보내는 처리는 검사-효과-상호작용(Checks-Effects-Interactions) 패턴으로 다루고 OpenZeppelin의 ReentrancyGuard 사용을 고려해 재진입 호출을 막아야 합니다.
- 오버플로·언더플로: Solidity 0.8.x는 기본적으로 산술 오버플로와 언더플로를 막아 줍니다.
- 수수료 검증: 전송 수수료를 충분히 보냈는지 확인하고 부족하면 거래를 되돌려야 합니다.
- 접근 제어: transferFee와 maxSupply는 소유자만 바꿀 수 있어야 하며 onlyOwner 같은 접근 제어 장치를 올바르게 적용해야 합니다.