반응형
ERC-6551, Token Bound Account
ERC-6551이란?
- ERC-6551 은 2023년 5월 7일 이더리움 메인넷에 적용된 표준.
- 이 표준은 NFT(Non-fungible Token)를 활용하여 TBA(Token Bound Account)를 생성하는 기능.
- 즉, TBA 는 NFT 가 소유하고 있는 지갑을 나타내며, 이 지갑 안에는 다양한 토큰(ethereum, ERC=20, ERC-721, ERC-1155) 를 보유할 수 있음.
- TBA 가 소유하고 있는 에셋들에 대한 권한은 NFT 소유하고 있는 지갑에서 갖게 되며, 만약 NFT 의 소유자가 변경될 시 해당 TBA 에 들어있는 자산들의 권한도 새로운 소유자가 가져가게 된다.
ERC-6551 구현하기
ERC-6551 구현을 위해 필요한 구성 요소는 두 가지이다.
- A permissionless registry: TBA 배포를 위한 레지스트리
- A standard interface: TBA 구현을 위한 인터페이스
A permissionless registry
- ERC721 NFT 지갑을 생성하고 연결하는 역할
- 레지스트리는 TBA 를 활용하고자 하는 프로젝트를 위한 단일 진입점 역할
- 코드는 깃을 참고 -> https://github.com/erc6551/reference/blob/main/src/ERC6551Registry.sol
- ERC6551 Registry Sample Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface IERC6551Registry {
/**
* @dev The registry MUST emit the ERC6551AccountCreated event upon successful account creation.
*/
event ERC6551AccountCreated(
address account,
address indexed implementation,
bytes32 salt,
uint256 chainId,
address indexed tokenContract,
uint256 indexed tokenId
);
/**
* @dev The registry MUST revert with AccountCreationFailed error if the create2 operation fails.
*/
error AccountCreationFailed();
/**
* @dev Creates a token bound account for a non-fungible token.
*
* If account has already been created, returns the account address without calling create2.
*
* Emits ERC6551AccountCreated event.
*
* @return account The address of the token bound account
*/
function createAccount(
address implementation,
bytes32 salt,
uint256 chainId,
address tokenContract,
uint256 tokenId
) external returns (address account);
/**
* @dev Returns the computed token bound account address for a non-fungible token.
*
* @return account The address of the token bound account
*/
function account(
address implementation,
bytes32 salt,
uint256 chainId,
address tokenContract,
uint256 tokenId
) external view returns (address account);
}
contract ERC6551Registry is IERC6551Registry {
function createAccount(
address implementation,
bytes32 salt,
uint256 chainId,
address tokenContract,
uint256 tokenId
) external returns (address) {
assembly {
// Memory Layout:
// ----
// 0x00 0xff (1 byte)
// 0x01 registry (address) (20 bytes)
// 0x15 salt (bytes32) (32 bytes)
// 0x35 Bytecode Hash (bytes32) (32 bytes)
// ----
// 0x55 ERC-1167 Constructor + Header (20 bytes)
// 0x69 implementation (address) (20 bytes)
// 0x5D ERC-1167 Footer (15 bytes)
// 0x8C salt (uint256) (32 bytes)
// 0xAC chainId (uint256) (32 bytes)
// 0xCC tokenContract (address) (32 bytes)
// 0xEC tokenId (uint256) (32 bytes)
// Silence unused variable warnings
pop(chainId)
// Copy bytecode + constant data to memory
calldatacopy(0x8c, 0x24, 0x80) // salt, chainId, tokenContract, tokenId
mstore(0x6c, 0x5af43d82803e903d91602b57fd5bf3) // ERC-1167 footer
mstore(0x5d, implementation) // implementation
mstore(0x49, 0x3d60ad80600a3d3981f3363d3d373d3d3d363d73) // ERC-1167 constructor + header
// Copy create2 computation data to memory
mstore8(0x00, 0xff) // 0xFF
mstore(0x35, keccak256(0x55, 0xb7)) // keccak256(bytecode)
mstore(0x01, shl(96, address())) // registry address
mstore(0x15, salt) // salt
// Compute account address
let computed := keccak256(0x00, 0x55)
// If the account has not yet been deployed
if iszero(extcodesize(computed)) {
// Deploy account contract
let deployed := create2(0, 0x55, 0xb7, salt)
// Revert if the deployment fails
if iszero(deployed) {
mstore(0x00, 0x20188a59) // `AccountCreationFailed()`
revert(0x1c, 0x04)
}
// Store account address in memory before salt and chainId
mstore(0x6c, deployed)
// Emit the ERC6551AccountCreated event
log4(
0x6c,
0x60,
// `ERC6551AccountCreated(address,address,bytes32,uint256,address,uint256)`
0x79f19b3655ee38b1ce526556b7731a20c8f218fbda4a3990b6cc4172fdf88722,
implementation,
tokenContract,
tokenId
)
// Return the account address
return(0x6c, 0x20)
}
// Otherwise, return the computed account address
mstore(0x00, shr(96, shl(96, computed)))
return(0x00, 0x20)
}
}
function account(
address implementation,
bytes32 salt,
uint256 chainId,
address tokenContract,
uint256 tokenId
) external view returns (address) {
assembly {
// Silence unused variable warnings
pop(chainId)
pop(tokenContract)
pop(tokenId)
// Copy bytecode + constant data to memory
calldatacopy(0x8c, 0x24, 0x80) // salt, chainId, tokenContract, tokenId
mstore(0x6c, 0x5af43d82803e903d91602b57fd5bf3) // ERC-1167 footer
mstore(0x5d, implementation) // implementation
mstore(0x49, 0x3d60ad80600a3d3981f3363d3d373d3d3d363d73) // ERC-1167 constructor + header
// Copy create2 computation data to memory
mstore8(0x00, 0xff) // 0xFF
mstore(0x35, keccak256(0x55, 0xb7)) // keccak256(bytecode)
mstore(0x01, shl(96, address())) // registry address
mstore(0x15, salt) // salt
// Store computed account address in memory
mstore(0x00, shr(96, shl(96, keccak256(0x00, 0x55))))
// Return computed account address
return(0x00, 0x20)
}
}
}
createAccount
- ERC6551 Registry에서는 createAccount 함수를 이용하여 TBA 를 생성할 수 있다.
1. createAccount는 다섯 개의 매개 변수를 받으며, 반환 값은 address 이다.
function createAccount(
address implementation, // 로직 컨트랙트 주소
bytes32 salt, // 계약 생성에 사용되는 salt
uint256 chainId, // chainId
address tokenContract, // NFT 컨트랙트 주소
uint256 tokenId // NFT 토큰 ID
) external returns (address) {}
2. 메모리 레이아웃 정의 -> 컨트랙트의 상태를 저장하는 데 사용될 메모리의 레이아웃을 정의한다.
assembly {
// Memory Layout:
// ----
// 0x00 0xff (1 byte)
// 0x01 registry (address) (20 bytes)
// 0x15 salt (bytes32) (32 bytes)
// 0x35 Bytecode Hash (bytes32) (32 bytes)
// ----
// 0x55 ERC-1167 Constructor + Header (20 bytes)
// 0x69 implementation (address) (20 bytes)
// 0x5D ERC-1167 Footer (15 bytes)
// 0x8C salt (uint256) (32 bytes)
// 0xAC chainId (uint256) (32 bytes)
// 0xCC tokenContract (address) (32 bytes)
// 0xEC tokenId (uint256) (32 bytes)
3. 컨트랙트 배포 -> 해당 주소가 배포된 적이 없다면, create2 를 사용하여 컨트랙트를 배포
// Compute account address
let computed := keccak256(0x00, 0x55)
// If the account has not yet been deployed
if iszero(extcodesize(computed)) {
// Deploy account contract
let deployed := create2(0, 0x55, 0xb7, salt)
// Revert if the deployment fails
if iszero(deployed) {
mstore(0x00, 0x20188a59) // `AccountCreationFailed()`
revert(0x1c, 0x04)
}
// Store account address in memory before salt and chainId
mstore(0x6c, deployed)
4. 컨트랙트가 정상적으로 생성되었다면, 아래와 같이 이벤트 발생
// Emit the ERC6551AccountCreated event
log4(
0x6c,
0x60,
// `ERC6551AccountCreated(address,address,bytes32,uint256,address,uint256)`
0x79f19b3655ee38b1ce526556b7731a20c8f218fbda4a3990b6cc4172fdf88722,
implementation,
tokenContract,
tokenId
)
A standard interface
- TBA 생성을 위한 로직 컨트랙트
- 사용한 코드는 아래 깃을 참고
- https://github.com/erc6551/reference/blob/main/src/examples/simple/ERC6551Account.sol
- 토큰 전송 등의 기능은 필요에 따라 추가 가능하고, 나는 transferERC721 을 아래와 같이 추가해서 테스트 해봄.
function transferERC721(address tokenContract, address to, uint256 tokenId) external {
require(msg.sender == owner());
IERC721(tokenContract).safeTransferFrom(address(this), to, tokenId);
}
반응형
'블록체인 > 블록체인이란?' 카테고리의 다른 글
Keccak256의 작동 원리 (0) | 2024.05.23 |
---|---|
ENS(Ethereum Name Service) (0) | 2024.05.21 |
ERC1155 란 무엇일까?! 스마트 컨트랙트 코드로 이해해보기. (0) | 2022.09.12 |
인적 문제로 발생할 수 있는 블록체인의 보안적 이슈, 크립토재킹 & 더스팅 공격 & 시빌 공격 (0) | 2022.08.05 |
이클립스 공격 (Eclipse Attack) (0) | 2022.08.05 |
댓글