본문 바로가기
블록체인/블록체인이란?

ERC-6551, Token Bound Account

by 제이제이_은재 2024. 5. 17.
반응형

 

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 구현을 위해 필요한 구성 요소는 두 가지이다.

  1. A permissionless registry: TBA 배포를 위한 레지스트리
  2. A standard interface: TBA 구현을 위한 인터페이스

 

A permissionless registry

// 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

function transferERC721(address tokenContract, address to, uint256 tokenId) external {
    require(msg.sender == owner());
    IERC721(tokenContract).safeTransferFrom(address(this), to, tokenId);
}
반응형

댓글