> ## Documentation Index
> Fetch the complete documentation index at: https://docs.optimism.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Interop 메시지 전달 튜토리얼

> L2ToL2CrossDomainMessenger 컨트랙트를 사용하여 메시지 전달 시스템을 구축함으로써 OP Stack 체인에서 크로스체인 통신을 구현하는 방법을 알아봅니다.

> [!NOTE]
>
> OP Stack 상호운용성(interop)은 Lagoon 하드포크와 함께 활성화됩니다.

## 개요

이 튜토리얼에서는 OP Stack 생태계 내에서 크로스체인 통신을 구현하는 방법을 보여 줍니다. `L2ToL2CrossDomainMessenger` 컨트랙트를 사용하여
서로 다른 체인이 상호작용할 수 있게 해 주는 완전한 메시지 전달 시스템을 구축합니다.

> [accordion] 이 튜토리얼 소개
>
> **필요한 사전 기술 지식**
>
> * 중급 수준의 Solidity 프로그래밍
> * 기본적인 TypeScript 지식
> * 스마트 컨트랙트 개발에 대한 이해
> * 블록체인 개념에 대한 이해
>
> **학습 내용**
>
> * 서로 다른 체인에 컨트랙트를 배포하는 방법
> * 크로스체인 메시지 전달을 구현하는 방법
> * 체인 간 발신자 검증을 처리하는 방법
> * 체인 간에 메시지를 수동으로 릴레이하는 방법
>
> **개발 환경**
>
> * Unix 계열 운영 체제(Linux, macOS 또는 Windows용 WSL)
> * Node.js 버전 16 이상
> * 버전 관리를 위한 Git
>
> **필요한 도구**
>
> 이 튜토리얼에서는 다음과 같은 주요 도구를 사용합니다.
>
> * Foundry: 스마트 컨트랙트 개발용
> * Supersim: 로컬 블록체인 시뮬레이션용(선택 사항)
> * TypeScript: 오프체인 코드용(메시지를 수동으로 릴레이하는 경우)
> * Viem: 오프체인 앱에서 체인과 상호작용하는 용도

### 빌드할 내용

* 인사말을 저장하고 업데이트하는 `Greeter` 컨트랙트
* 인사말을 업데이트하기 위해 크로스체인 메시지를 보내는 `GreetingSender` 컨트랙트
* 체인 간에 메시지를 릴레이하는 TypeScript 애플리케이션

> [!NOTE]
>
> 이 튜토리얼은 크로스체인 메시징을 구현하기 위한 단계별 안내를 제공합니다.
> 개념적인 개요는
> [메시지 전달 설명](https://docs.optimism.io/ko/app-developers/guides/interoperability/message-passing.md)을 참조하십시오.

이 튜토리얼에서는 [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) 컨트랙트를 사용하여 상호운용 가능한 블록체인 간에 메시지를 전달하는 방법을 알아봅니다.

## 개발 환경 설정하기

**1. [Supersim 설치하기](https://docs.optimism.io/ko/app-developers/tutorials/development/supersim/installation.md)를 따라 다음을 설정합니다.**

* 스마트 컨트랙트 개발을 위한 Foundry(모든 경우에 필요)
* 로컬 블록체인 시뮬레이션을 위한 Supersim(선택 사항)

**2. 설치를 확인합니다.**

```sh theme={null}
forge --version
./supersim --version
```

## 온체인 메시지 전달 구현하기 (Solidity)

구현은 세 가지 주요 구성 요소로 이루어집니다.

1. **Greeter 컨트랙트**: `Chain B`에 배포되며, 메시지를 수신하고 저장합니다.
2. **GreetingSender 컨트랙트**: `Chain A`에 배포되며, 크로스체인 메시지를 시작합니다.

**1. 테스트 네트워크 설정하기**

1. [Supersim](https://docs.optimism.io/ko/app-developers/tools-sdks/supersim.md)을 사용하는 경우 Supersim이 설치된 디렉터리로 이동하여 autorelay를 활성화한 상태로 시작합니다.

   ```sh theme={null}
   ./supersim --interop.autorelay
   ```

   [devnet](https://docs.optimism.io/ko/app-developers/guides/building-apps.md)을 사용하는 경우 이 단계를 건너뛰십시오.

   > [tab] Supersim
   >
   > Supersim은 세 개의 `anvil` 블록체인을 생성합니다.
   >
   > | 역할 | ChainID | RPC URL |
   > | - | -: | - |
   > | L1 | 900 | [http://127.0.0.1:8545](http://127.0.0.1:8545) |
   > | OPChainA | 901 | [http://127.0.0.1:9545](http://127.0.0.1:9545) |
   > | OPChainB | 902 | [http://127.0.0.1:9546](http://127.0.0.1:9546) |

   > [tab] Devnets
   >
   > devnet에 포함된 세 개의 네트워크는 다음과 같습니다.
   >
   > | 역할 | ChainID | RPC URL |
   > | - | -: | - |
   > | L1 (Sepolia) | 11155111 | [https://eth-sepolia.public.blastapi.io](https://eth-sepolia.public.blastapi.io) |
   > | ChainA | 420120000 | [https://interop-alpha-0.optimism.io](https://interop-alpha-0.optimism.io) |
   > | ChainB | 420120001 | [https://interop-alpha-1.optimism.io](https://interop-alpha-1.optimism.io) |

2. 별도의 셸에서 구성을 환경 변수에 저장합니다.

   > [tab] Supersim
   >
   > Supersim에 대해 다음 파라미터를 설정합니다.
   >
   > ```sh theme={null}
   > PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
   > USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
   > URL_CHAIN_A=http://127.0.0.1:9545
   > URL_CHAIN_B=http://127.0.0.1:9546
   > INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
   > ```

   > [tab] Devnets
   >
   > Devnet의 경우 `PRIVATE_KEY`에 설정 스크립트에 사용한 개인 키를 지정한 다음, 다음 파라미터를 설정합니다.
   >
   > ```sh theme={null}
   > USER_ADDRESS=`cast wallet address --private-key $PRIVATE_KEY`
   > URL_CHAIN_A=https://interop-alpha-0.optimism.io
   > URL_CHAIN_B=https://interop-alpha-1.optimism.io
   > INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
   > ```

> [accordion] 동작 확인
>
> 체인이 실행 중인지 확인하려면 `$USER_ADDRESS`의 잔액을 확인합니다.
>
> ```sh theme={null}
> cast balance --ether $USER_ADDRESS --rpc-url $URL_CHAIN_A
> cast balance --ether $USER_ADDRESS --rpc-url $URL_CHAIN_B
> ```

**2. 컨트랙트 생성하기**

1. 새 Foundry 프로젝트를 생성합니다.

   ```sh theme={null}
   mkdir onchain-code
   cd onchain-code
   forge init
   ```

2. `src/Greeter.sol`에 다음 파일을 넣습니다.
   이 컨트랙트는 [Hardhat의 Greeter 컨트랙트](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol)를 변형한 것입니다.

   ```solidity theme={null}
       //SPDX-License-Identifier: MIT
       pragma solidity ^0.8.0;

       contract Greeter {
           string greeting;

           event SetGreeting(
               address indexed sender,     // msg.sender
               string greeting
           );

           function greet() public view returns (string memory) {
               return greeting;
           }

           function setGreeting(string memory _greeting) public {
               greeting = _greeting;
               emit SetGreeting(msg.sender, _greeting);
           }
       }
   ```

3. `Greeter` 컨트랙트를 Chain B에 배포하고, 생성된 컨트랙트 주소를 `GREETER_B_ADDRESS` 환경 변수에 저장합니다.

   ```sh theme={null}
   GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
   ```

   > [accordion] 설명
   >
   > 컨트랙트를 배포하는 명령은 다음과 같습니다.
   >
   > ```sh theme={null}
   > forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast
   > ```
   >
   > 명령 출력에는 배포자 주소, 새 컨트랙트의 주소, 트랜잭션 해시가 표시됩니다.
   >
   > ```
   > Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
   > Deployed to: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
   > Transaction hash: 0xf155d360ec70ee10fe0e02d99c16fa5d6dc2a0e79b005fec6cbf7925ff547dbf
   > ```
   >
   > [`awk`](https://www.tutorialspoint.com/awk/index.htm) 명령은 `Deployed to:`가 포함된 줄을 찾아 해당 줄의 세 번째 단어, 즉 주소를 출력합니다.
   >
   > ```sh theme={null}
   > awk '/Deployed to:/ {print $3}'
   > ```
   >
   > 마지막으로, UNIX(Linux 및 macOS 포함)에서 명령줄에 백틱이 포함되어 있으면 셸은 백틱 사이의 코드를 실행하고 그 출력(이 경우 컨트랙트 주소)을 명령에 넣습니다.
   > 따라서 다음과 같은 결과가 됩니다.
   >
   > ```sh theme={null}
   > GREETER_B_ADDRESS=<the address>
   > ```

> [accordion] 동작 확인
>
> 다음 명령을 실행하여 컨트랙트가 작동하는지 확인합니다.
> 첫 번째와 세 번째 명령은 현재 인사말을 조회하고, 두 번째 명령은 인사말을 업데이트합니다.
>
> ```sh theme={null}
> cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
> cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "setGreeting(string)" Hello$$
> cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
> ```

4. 프로젝트에 Optimism Solidity 라이브러리를 설치합니다.

   ```sh theme={null}
   cd lib
   npm install @eth-optimism/contracts-bedrock
   cd ..
   echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt
   ```

5. `src/GreetingSender.sol`을 생성합니다.

   ```solidity theme={null}
       //SPDX-License-Identifier: MIT
       pragma solidity ^0.8.0;

       import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol";
       import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/src/L2/IL2ToL2CrossDomainMessenger.sol";

       import { Greeter } from "src/Greeter.sol";

       contract GreetingSender {
           IL2ToL2CrossDomainMessenger public immutable messenger =
               IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);

           address immutable greeterAddress;
           uint256 immutable greeterChainId;

           constructor(address _greeterAddress, uint256 _greeterChainId) {
               greeterAddress = _greeterAddress;
               greeterChainId = _greeterChainId;
           }

           function setGreeting(string calldata greeting) public {
               bytes memory message = abi.encodeCall(
                   Greeter.setGreeting,
                   (greeting)
               );
               messenger.sendMessage(greeterChainId, greeterAddress, message);
           }
       }
   ```

   > [accordion] 설명
   >
   > ```solidity theme={null}
   >      function setGreeting(string calldata greeting) public {
   >          bytes memory message = abi.encodeCall(
   >              Greeter.setGreeting,
   >              (greeting)
   >          );
   >          messenger.sendMessage(greeterChainId, greeterAddress, message);
   >      }
   > ```
   >
   > 이 함수는 `setGreeting`에 대한 호출을 인코딩하여 다른 체인의 컨트랙트로 보냅니다.
   > `abi.encodeCall(Greeter.setGreeting, (greeting))`은 함수 selector와 파라미터를 인코딩하여 [calldata](https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html)를 구성합니다.
   > 인코딩된 메시지는 `messenger.sendMessage`로 전달되며, 이 함수는 메시지를 지정된 체인(`greeterChainId`)의 대상 컨트랙트(`greeterAddress`)로 전달합니다.
   >
   > 이를 통해 (메시지를 릴레이할 실행 메시지가 있는 한) 제공된 `greeting` 값으로 `setGreeting`이 원격으로 실행됩니다.

6. `GreetingSender`를 chain A에 배포합니다.

   ```sh theme={null}
   CHAIN_ID_B=`cast chain-id --rpc-url $URL_CHAIN_B`
   GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'`
   ```

**3. 메시지 보내기**

chain A에서 chain B로 인사말을 보냅니다.

```sh theme={null}
cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A"
sleep 4
cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
```

`sleep` 호출이 필요한 이유는 트랜잭션이 chain A에 포함되기까지 최대 2초가 걸릴 수 있고, 그 후 릴레이 트랜잭션이 chain B에 포함되기까지 다시 최대 2초가 걸릴 수 있기 때문입니다.

## 발신자 정보

다음 명령을 실행하여 이벤트를 확인하고 누가 `setGreeting`을 호출했는지 확인합니다.

```sh theme={null}
cast logs --rpc-url $URL_CHAIN_B 'SetGreeting(address,string)'
```

발신자 정보는 두 번째 이벤트 topic에 저장됩니다.
그러나 크로스체인 메시지의 경우 이 값은 로컬 `L2ToL2CrossDomainMessenger` 컨트랙트 주소(`4200000000000000000000000000000000000023`)에 해당하므로, 원래 발신자를 식별하는 데는 효과적이지 않습니다.

이 섹션에서는 크로스 도메인 메시지를 수신할 때 발신자의 신원(주소 및 체인 ID)과 함께 별도의 이벤트를 발생시키도록 `Greeter.sol`을 변경합니다.

**1. Greeter 컨트랙트 수정하기**

1. `src/Greeter.sol`을 다음 코드로 수정합니다.

   ```solidity theme={null}
       //SPDX-License-Identifier: MIT
       pragma solidity ^0.8.0;

       import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol";

       interface IL2ToL2CrossDomainMessenger {
           function crossDomainMessageContext() external view returns (address sender_, uint256 source_);
       }

       contract Greeter {

           IL2ToL2CrossDomainMessenger public immutable messenger =
               IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);

           string greeting;

           event SetGreeting(
               address indexed sender,     // msg.sender
               string greeting
           );

           event CrossDomainSetGreeting(
               address indexed sender,   // Sender on the other side
               uint256 indexed chainId,  // ChainID of the other side
               string greeting
           );

           function greet() public view returns (string memory) {
               return greeting;
           }

           function setGreeting(string memory _greeting) public {
               greeting = _greeting;
               emit SetGreeting(msg.sender, _greeting);

               if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
                   (address sender, uint256 chainId) =
                       messenger.crossDomainMessageContext();
                   emit CrossDomainSetGreeting(sender, chainId, _greeting);
               }
           }
       }
   ```

   > [accordion] 설명
   >
   > ```solidity theme={null}
   > interface IL2ToL2CrossDomainMessenger {
   >   function crossDomainMessageContext() external view returns (address sender_, uint256 source_);
   > }
   > ```
   >
   > 이 정의는 작성 시점에 [npmjs 패키지](https://www.npmjs.com/package/@eth-optimism/contracts-bedrock)에 포함되어 있지 않으므로 여기에 직접 추가합니다.
   >
   > ```solidity theme={null}
   >         if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
   >             (address sender, uint256 chainId) =
   >                 messenger.crossDomainMessageContext();
   >             emit CrossDomainSetGreeting(sender, chainId, _greeting);
   >         }
   > ```
   >
   > `L2ToL2CrossDomainMessenger`로부터 메시지를 받았다면 `L2ToL2CrossDomainMessenger.crossDomainMessageContext`를 호출합니다.

2. 컨트랙트를 다시 배포합니다.
   `GreetingSender`에서 `Greeter`의 주소는 변경할 수 없으므로(immutable) 두 컨트랙트를 모두 다시 배포해야 합니다.

   ```sh theme={null}
   GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
   GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'`
   ```

**2. 크로스체인 발신자 정보를 확인할 수 있는지 검증하기**

1. `GreetingSender`를 통해 인사말을 설정합니다.

   ```sh theme={null}
   cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
   cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A, with a CrossDomainSetGreeting event"
   sleep 4
   cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
   ```

2. 로그 항목을 읽습니다.

   ```sh theme={null}
   cast logs --rpc-url $URL_CHAIN_B 'CrossDomainSetGreeting(address,uint256,string)'
   echo $GREETER_A_ADDRESS
   echo 0x385=`echo 0x385 | cast --to-dec`
   echo 0x190a85c0=`echo 0x190a85c0 | cast --to-dec`
   ```

   두 번째 topic(첫 번째 indexed 로그 파라미터)이 `$GREETER_A_ADDRESS`와 같은지 확인합니다.
   세 번째 topic은 supersim chain A의 체인 ID인 `0x385=901` 또는 devnet alpha 0의 체인 ID인 `0x190a85c0=420120000` 중 하나입니다.

## 다음 단계

* 상호운용성에 관한 일반적인 질문에 대한 답변은 [OP Stack Interop 설명](https://docs.optimism.io/ko/op-stack/interop/explainer.md)을 검토하십시오.
* 내부적으로 어떤 일이 일어나는지 이해하려면 [메시지 전달 설명](https://docs.optimism.io/ko/app-developers/guides/interoperability/message-passing.md)을 읽어 보십시오.
* OP Stack 생태계 내에서 여러 블록체인을 사용하는 혁신적인 앱을 작성해 보십시오.
