ERC-3643 증권형 토큰 표준 T-REX (T-REX - Token for Regulated EXchanges)
Final · 토큰 · 2021-07-09 작성
주식 같은 규제 대상 자산을 나타내는 토큰이 자격을 확인받은 투자자 사이에서만 오가도록, 전송할 때마다 온체인 신원과 규정 준수를 자동 검사하는 허가형 토큰 표준입니다.
왜 필요한가
주식이나 부동산 같은 자산을 토큰으로 만들면 증권법을 지켜야 합니다. 아무나 토큰을 보유할 수 없고 발행자는 누가 토큰을 들고 있는지 추적하면서 자격을 갖춘 투자자만 받을 수 있게 해야 합니다. 그런데 기존 ERC-20 토큰은 누구나 자유롭게 주고받는 구조여서 이런 요건을 강제할 수 없습니다. 전송 상대가 신원 확인(KYC)을 마쳤는지, 국가별 제한이나 1인당 보유 한도 같은 규칙을 지키는지 검사할 표준 방법이 없기 때문입니다. 증권 관리에 필요한 토큰 동결, 일시정지, 분실 지갑 복구 같은 기능도 표준에 없습니다. ERC-3643은 토큰 컨트랙트가 전송 때마다 온체인 신원 시스템과 규정 준수 컨트랙트를 거치게 하는 인터페이스 묶음을 정의해 증권형 토큰의 발행부터 유통까지 전 과정을 온체인에서 관리할 수 있게 합니다.
구현해야 하는 것
function addAgent(address _agent) external;: 소유자(owner)가 운영을 맡을 에이전트 주소를 추가합니다.function removeAgent(address _agent) external;: 소유자가 에이전트 주소를 제거합니다.function isAgent(address _agent) external view returns (bool);: 해당 주소가 에이전트인지 확인합니다.function onchainID() external view returns (address);: 토큰에 연결된 온체인 신원 컨트랙트 주소를 돌려줍니다.function identityRegistry() external view returns (IIdentityRegistry);: 토큰이 사용하는 Identity Registry 컨트랙트를 돌려줍니다.function compliance() external view returns (ICompliance);: 토큰에 묶인 Compliance 컨트랙트를 돌려줍니다.function paused() external view returns (bool);: 토큰이 일시정지 상태인지 알려 줍니다.function isFrozen(address _userAddress) external view returns (bool);: 해당 지갑 주소가 통째로 동결됐는지 알려 줍니다.function getFrozenTokens(address _userAddress) external view returns (uint256);: 해당 지갑에서 동결된 토큰 수량을 돌려줍니다.function pause() external;: 토큰 전체의 전송을 일시정지합니다.function unpause() external;: 일시정지를 해제합니다.function setAddressFrozen(address _userAddress, bool _freeze) external;: 특정 지갑 주소 전체를 동결하거나 해제합니다.function freezePartialTokens(address _userAddress, uint256 _amount) external;: 지갑 잔액 중 일부 수량만 동결합니다.function unfreezePartialTokens(address _userAddress, uint256 _amount) external;: 부분 동결된 수량을 해제합니다.function setIdentityRegistry(address _identityRegistry) external;: 토큰이 사용할 Identity Registry를 지정합니다.function setCompliance(address _compliance) external;: 토큰에 적용할 Compliance 컨트랙트를 지정합니다.function forcedTransfer(address _from, address _to, uint256 _amount) external returns (bool);: 에이전트가 투자자 지갑에서 강제로 토큰을 전송합니다. 수신자의 신원 검증만 요구하고 컴플라이언스 규칙은 우회합니다.function mint(address _to, uint256 _amount) external;: 검증된 수신자에게 새 토큰을 발행합니다.function burn(address _userAddress, uint256 _amount) external;: 해당 지갑의 토큰을 소각합니다. 자격 검사를 모두 우회합니다.function recoveryAddress(address _lostWallet, address _newWallet, address _investorOnchainID) external returns (bool);: 개인 키를 잃은 투자자의 토큰을 온체인 신원을 근거로 새 지갑으로 복구합니다.function batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external;: 여러 건의 전송을 한 트랜잭션으로 묶어 처리합니다.function batchForcedTransfer(address[] calldata _fromList, address[] calldata _toList, uint256[] calldata _amounts) external;: 여러 건의 강제 전송을 한 번에 처리합니다.event RecoverySuccess(address _lostWallet, address _newWallet, address _investorOnchainID);: 지갑 복구가 성공했을 때 기록되는 이벤트입니다.event AddressFrozen(address indexed _userAddress, bool indexed _isFrozen, address indexed _owner);: 지갑 주소의 동결 상태가 바뀔 때 기록되는 이벤트입니다.function registerIdentity(address _userAddress, IIdentity _identity, uint16 _country) external;: 에이전트가 투자자의 지갑 주소, Identity 컨트랙트, 국가 코드를 Identity Registry에 등록합니다.function isVerified(address _userAddress) external view returns (bool);: 지갑이 레지스트리에 있고 연결된 Identity 컨트랙트가 요구 클레임을 승인된 발급자의 서명과 함께 갖고 있는지 확인합니다.function investorCountry(address _userAddress) external view returns (uint16);: 투자자의 거주 국가 코드를 돌려줍니다.function canTransfer(address _from, address _to, uint256 _amount) external view returns (bool);: 전송이 토큰 전체에 적용되는 규정(보유자 수 상한, 투자자당 한도 등)에 맞는지 미리 확인합니다.function transferred(address _from, address _to, uint256 _amount) external;: 전송이 실행된 뒤 Compliance 컨트랙트의 상태를 갱신하도록 알립니다.function addTrustedIssuer(IClaimIssuer _trustedIssuer, uint[] calldata _claimTopics) external;: 신뢰할 클레임 발급자와 그 발급자가 다룰 클레임 주제를 등록합니다.function addClaimTopic(uint256 _claimTopic) external;: 투자자 신원이 갖춰야 할 클레임 주제를 추가합니다.
심층 설명
표준이 요구하는 기능
ERC-3643 토큰은 ERC-20과 호환되어야 하고 온체인 신원 시스템과 함께 써야 합니다. 그 위에 규제 기관이나 발행자가 요구하는 어떤 규정도 적용할 수 있어야 하고 전송을 보내기 전에 성공 여부를 확인하는 표준 인터페이스, 개인 키 분실 시 복구 시스템, 투자자 지갑의 부분·전체 동결, 토큰 일시정지, 발행과 소각, Agent와 Owner 역할 정의, 에이전트 지갑의 강제 전송, 가스를 아끼고 같은 블록에서 처리되는 배치 트랜잭션을 지원해야 합니다. ERC-20 함수를 모두 호출할 수 있지만 허가형 구조라서 모든 전송에 신원과 규정 검사가 붙는 점이 다릅니다.
전송이 성립하는 조건
transfer가 성공하려면 보내는 사람이 동결분을 뺀 자유 잔액(총 잔액 - 동결 토큰)을 충분히 갖고 있어야 하고 받는 사람이 Identity Registry에 화이트리스트로 등록되고 필요한 클레임을 갖춰 검증된 상태여야 합니다. 양쪽 지갑 모두 동결되지 않아야 하고 토큰이 일시정지 상태가 아니어야 하며 Compliance 컨트랙트의 canTransfer가 TRUE를 돌려줘야 합니다. transferFrom도 같은 방식으로 동작합니다. mint와 forcedTransfer는 수신자의 등록과 검증만 요구하고 컴플라이언스 규칙은 우회하며 burn은 자격 검사를 모두 우회합니다.
isVerified와 canTransfer의 역할 분담
두 검사는 다른 질문에 답합니다. isVerified는 투자자 개인의 자격을 봅니다. 수신자 지갑이 토큰의 Identity Registry에 있는지, 연결된 Identity 컨트랙트가 Claim Topics Registry가 요구하는 클레임을 갖고 있는지, 그 클레임이 Trusted Issuers Registry에 등록된 발급자의 서명을 받았는지 확인합니다. canTransfer는 토큰 전체에 걸린 규정을 봅니다. 최대 보유자 수(국가별 제한 포함)나 투자자당 최대 보유량 같은 규칙에 이번 전송이 맞는지 확인합니다. 둘 다 전송 함수 안에서 호출되고 ERC-20의 balanceOf·allowance처럼 전송 전에 미리 조회해 실패를 피하는 용도로도 쓸 수 있습니다.
여섯 개 컨트랙트로 이뤄진 구조
표준은 Token, Identity Registry, Identity Registry Storage, Compliance, Trusted Issuers Registry, Claim Topics Registry의 인터페이스를 정의합니다. Identity Registry는 지갑 주소, Identity 컨트랙트, ISO-3166 국가 코드를 연결하며 토큰마다 하나씩 둡니다. Identity Registry Storage는 화이트리스트 저장을 레지스트리 기능과 분리한 것으로, 여러 Identity Registry에 바인딩되어 투자자 명단을 공유하면서도 토큰마다 다른 발급자·클레임 요건을 둘 수 있게 합니다. Compliance는 발행 조건의 규칙을 담아 매 거래마다 호출되며 맞춤형으로 만들거나 외부 Module을 붙였다 떼는 모듈형으로 배포할 수 있습니다.
Owner와 Agent 역할
소유권은 ERC-173으로 정의하고 owner(토큰 발행자)가 에이전트를 임명하고 해임합니다. Token과 Identity Registry 역할을 하는 컨트랙트는 IAgentRole 인터페이스와 호환되어야 합니다. Identity Registry에서 신원을 넣고 빼는 일은 에이전트만 할 수 있고 owner가 스스로를 에이전트로 지정해 전권을 유지할 수도 있습니다. 에이전트 기능을 표준 인터페이스에 넣은 것은 의도적인 설계로, 외부 소유 계정만이 아니라 자동화 시스템이나 스마트 컨트랙트가 에이전트가 되어 환매 요청에 맞춰 토큰을 소각하거나 사기 행위에 연루된 지갑을 동결하는 식으로 조건에 따라 발행·소각·동결을 자동 실행하는 시나리오를 지원합니다.
복구, 동결, 일시정지, 배치 처리
recoveryAddress는 개인 키를 잃은 투자자의 토큰을 분실 지갑에서 새 지갑으로 옮기며 투자자의 온체인 신원(_investorOnchainID)을 근거로 삼습니다. 복구된 토큰의 이력은 투명성을 위해 블록체인에 보존됩니다. 동결은 두 단계로, setAddressFrozen은 지갑 주소 전체를, freezePartialTokens는 잔액 중 일부 수량만 동결합니다. pause와 unpause는 토큰 전체의 전송을 멈추거나 재개합니다. batchTransfer, batchMint, batchBurn, batchForcedTransfer, batchSetAddressFrozen 같은 배치 함수는 가스를 아끼고 여러 작업을 같은 블록에서 처리하기 위한 것입니다.
감사와 표준 상태
이 명세는 Kapersky와 Hacken의 감사를 받았고 주목할 만한 보안 문제는 발견되지 않았습니다. 감사는 주로 Tokeny의 구현을 대상으로 했지만 T-REX 표준의 핵심 원칙도 함께 검증했다고 원문은 밝힙니다. 표준 상태는 Final로 확정되어 더 이상 내용이 바뀌지 않습니다.