옵티미즘 초보자 가이드 2편 : ERC20 써보기

Darren K
Tokamak Network
Published in
12 min readMay 20, 2021

TL;DR

Run optimistic rollup

$ cd optimism/ops
$ docker-compose up -d

Build repository

$ cd ../
$ git clone https://github.com/Onther-Tech/l1-l2-deposit-withdrawal.git
$ cd l1-l2-deposit-withdrawal
$ yarn && yarn compile

Test ERC20 L1 <> L2

$ node ./example.js

옵티미스틱 롤업 사용하기 시리즈는 총 세 개의 포스팅으로 구성됩니다.

옵티미스틱 롤업 사용하기
옵티미스틱 롤업에서 ERC20 사용하기
옵티미스틱 롤업에서 ERC721 사용하기

소개

옵티미스틱 롤업은 스마트 컨트랙트를 지원하는 레이어2 솔루션으로, 이더리움 메인넷에서 사용하던 스마트 컨트랙트들을 레이어2에서도 동일하게 사용할 수 있도록 합니다. 레이어2에서 스마트 컨트랙트를 사용하고자 하는 사용자들은 자신의 토큰들을 레이어2에서 사용하게 됩니다. 레이어2의 dApp을 사용하기 위해서는 필요한 토큰을 메인넷으로부터 가져와야 하는데, 메인넷의 토큰을 컨트랙트에 예치시키고 같은 양의 토큰을 레이어2에서 받는 방식으로 이루어집니다. 또한, 레이어2에서 사용하던 토큰을 메인넷으로 다시 가져오는 인출 기능도 지원합니다.

레이어2에 갖고 있던 토큰을 메인넷으로 인출하는 부분에서는 레이어2 솔루션별로 소요 기간의 차이가 있습니다. 레이어2의 트랜잭션에 대한 검증 방식과 검증 기간에 따라 달라지는데, 토큰 인출 시도가 올바른지, 이중 지불이 될만한 시도인지 등을 검증하기 위함입니다. 옵티미스틱 롤업은 레이어2의 트랜잭션에 대한 정보를 메인넷의 컨트랙트에 일단 등록해두고, 올바르지 않은 트랜잭션을 찾기 위해 일정 기간 동안 검증 기간을 갖는데, 이 기간 동안 부정 행위가 발견되지 않은 트랜잭션은 올바른 트랜잭션으로써 확정됩니다. 토큰의 인출도 같은 과정으로 이루어지는데, 사용자의 토큰 인출 요청 트랜잭션이 실행되면서 동시에 메인넷의 토큰이 인출되는 것이 아니라, 토큰 인출 요청 트랜잭션이 발생한 뒤 일정 기간이 지나고 메인넷의 토큰을 받을 수 있습니다.

이 포스팅은 옵티미스틱 롤업을 사용하고자 하는 개발자 분들께, 레이어1과 레이어2의 ERC20 토큰 이동이 어떤 방식으로 이루어지며 어떻게 사용할 수 있는지를 전달하기 위해 작성되었습니다.

옵티미스틱 롤업은 web3.js 대신 ethers.js를, truffle 대신 hardhat을 사용하고 있습니다. ethers.js와 hardhat 등은 web3와 truffle 등과 비교했을 때 여러 장점들이 있어 최근 많은 스마트 컨트랙트 프로젝트에서 사용하고 있습니다.

저장소 정보

Optimistic rollup repository : https://github.com/ethereum-optimism/optimism

Commit : 751e2be

Test repository : https://github.com/Onther-Tech/l1-l2-deposit-withdrawal

Commit : a00fb50

인출/예치 코드

레이어1, 레이어2간의 ERC20 토큰 이동이 어떻게 이루어지는지 살펴보겠습니다. 먼저 레이어1에서 레이어2로 예치하는 것을 살펴보면, 개념적으로는 사용자가 레이어1에 가지고 있던 토큰이 레이어2로 이동한 것이지만, 내부적으로 레이어1 입장에서는 사용자의 토큰이 옵티미스틱 롤업의 게이트웨이 컨트랙트로 이동한 것이고, 레이어2 입장에서는 같은 양의 토큰이 사용자에게 발행된 것입니다. 따라서 사용자 입장에서는 레이어1에서 예치한 토큰 양만큼 레이어2에서 받기 때문에 레이어1 → 레이어2로 토큰 이동을 한 것으로도 볼 수 있습니다.

마찬가지로 레이어2에서 레이어1으로 토큰을 이동하는 것은, 레이어2 입장에서는 사용자의 토큰을 소각하는 것이고, 같은 양의 토큰을 레이어1에서 사용자에게 전송하는 것입니다.

아래에서는 테스트 코드를 기준으로 어떤 컨트랙트를 배포해서 사용하고, 사용자의 트랜잭션이 어떤 흐름으로 처리되는지 살펴보겠습니다. 테스트 코드의 위치는 l1-l2-deposit-withdrawal 저장소의 example.js 파일입니다.

컨트랙트와 서비스 설정

테스트 코드의 시작 부분에서는 먼저 테스트에 사용할 RPC Provider와 지갑 등을 설정합니다. RPC Provider와 지갑 모두 레이어1과 레이어2를 각각 설정하고 있습니다. l1MessengerAddress는 optimism의 도커 이미지들을 올릴 때 배포되는 컨트랙트의 주소로 하드코딩되어 있고, l2MessengerAddress는 레이어2의 predeployed 컨트랙트로 사용하는 주소가 하드코딩되어 있습니다.

옵티미스틱 롤업을 실행하는 환경이 localhost가 아닐 경우 RPC Provider에 주소를 직접 지정해주면 됩니다. 레이어1, 레이어2에 대한 RPC Provider 주소를 지정해 줄 수 있습니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

다음으로 Watcher 서비스를 연결하고 있습니다. Watcher 서비스는 사용자가 트랜잭션을 보낸 뒤 완전한 처리가 끝나도록 대기할 수 있도록 합니다. 사용자가 레이어1이나 레이어2로 보낸 트랜잭션이 단순히 해당 체인에서만 실행이 끝나는 것이 아니라 다른 체인에 영향을 미치는 경우(예를 들어 예치와 인출), 단순히 처음 트랜잭션을 보낸 체인에서 마이닝되었다고 처리가 끝난 것이 아니기 때문에 이와 같이 후처리를 대기할 수 있도록 해야 합니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

다음으로 테스트에 사용할 컨트랙트들을 배포하고 설정합니다. 여기서 배포하는 컨트랙트는 모두 세 개인데, 레이어1에서 두 개(L1_ERC20, L1_ERC20Gateway), 레이어2에서 한 개(L2_ERC20)의 컨트랙트들을 배포합니다.

  • L1_ERC20(레이어1 배포)

테스트에 사용할 ERC20 토큰 컨트랙트입니다. 옵티미스틱 롤업에서 필요한 기능이 추가된 것이 아닌, 최소한의 기능만 구현된 일반적인 ERC20 컨트랙트입니다.

  • L1_ERC20Gateway(레이어1 배포)

레이어1 L1_ERC20 토큰의 게이트웨이 역할을 담당하는 컨트랙트입니다. 사용자가 레이어2로 이동시키고자 하는 레이어1 토큰은 이 게이트웨이 컨트랙트에서 보관하고, 레이어2에서 레이어1로 인출이 완료될 때 이 게이트웨이 컨트랙트에 보관되었던 토큰을 사용자에게 전송합니다.

  • L2_ERC20(레이어2배포)

레이어2에 배포되는 ERC20 토큰 컨트랙트입니다. L1_ERC20 컨트랙트는 단순한 ERC20 토큰 컨트랙트이지만, L2_ERC20 토큰 컨트랙트는 옵티미스틱 롤업의 Abs_L2DepositedToken 컨트랙트를 상속받고 필요한 기능이 구현된 컨트랙트입니다. Abs_L2DepositedToken 컨트랙트에는 L1 <> L2 이동시 호출되는 함수의 인터페이스가 정의되어 있습니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

다음은 테스트에 필요한 컨트랙트 환경 구성을 마친 후, 본격적인 테스트를 시작하기 전에 토큰 잔고를 확인하는 부분입니다. 테스트에 사용할 ERC20 토큰의 잔고를 레이어1과 레이어2에서 각각 출력하는 부분이며, 레이어1에서는 1234, 레이어2에서는 0의 토큰을 가진 상태입니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

레이어1 토큰의 예치

레이어1 사용자가 가진 토큰을 예치하기 위해서는 먼저 approve를 해두어야 합니다. approve 대상은 L1_ERC20Gateway 컨트랙트이며, 이 테스트에서는 1234만큼의 토큰 양을 이동하고 있습니다. 실제 토큰의 이동은 L1_ERC20Gateway에서 deposit 함수를 호출하는 시점에 이루어지는데, 이 시점에 L1_ERC20Gateway 컨트랙트가 사용자의 토큰을 가져올 수 있도록 하기 위해 approve를 하고 있습니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

approve가 완료된 후 예치는 L1_ERC20Gateway 컨트랙트의 deposit 함수를 통해 이루어집니다. 이 때, deposit을 호출하는 트랜잭션이 마이닝되었다는 것은 레이어1의 사용자 토큰이 레이어1의 L1_ERC20Gateway 컨트랙트로 예치되었다는 것을 의미합니다. 또한 이것은 레이어2로 토큰 이동이 완료되었다는 것을 의미하지는 않기 때문에 레이어2의 후처리를 대기하는 작업이 필요합니다. 테스트 코드에서는 아래와 같이 L1_ERC20Gateway 컨트랙트의 deposit 함수를 호출하고, 이후 해당 트랜잭션을 기준으로 watcher를 통해 레이어2의 처리가 완료되기를 대기하는 것을 볼 수 있습니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

위의 코드가 완료된 뒤 다시 토큰 잔액을 확인하는 코드입니다. 지금까지의 과정이 정상적으로 완료된 경우, 레이어1의 사용자 토큰은 L1_ERC20Gateway 컨트랙트로 이동되어서 레이어1의 토큰 잔액은 0이 되어야 하고, 레이어2에서는 같은 양의 토큰이 사용자에게 발행되어 레이어2의 토큰 잔액은 1234가 되어야 합니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

레이어2 토큰의 출금

이제 레이어2의 토큰을 출금하여 다시 레이어1으로 가져오는 부분입니다. 레이어2 토큰의 출금은 사용자가 가진 레이어2의 토큰을 소각하고, 레이어1에서 같은 양의 토큰을 사용자에게 전송하는 과정으로 이루어집니다. 이 때, 레이어2의 트랜잭션은 L2_ERC20 토큰 컨트랙트를 통해 이루어지는데, 옵티미스틱 롤업의 컨트랙트를 상속받고 필요한 기능이 구현된 컨트랙트이기 때문에 미리 approve를 할 필요가 없습니다.

아래 코드에서 L2_ERC20의 withdraw 함수를 호출하고, 이 트랜잭션 실행이 완료된 뒤 그에 대응하는 레이어1의 처리를 대기하는 것을 볼 수 있습니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

출금이 완료된 뒤 다시 토큰의 잔액을 조회합니다. 레이어2의 사용자 토큰은 소각되어 레이어2토큰 잔액은 0이 되고, 같은 양의 토큰이 L1_ERC20Gateway 컨트랙트로부터 사용자에게 전송되어 레이어1토큰 잔액은 1234가 되는 것을 볼 수 있습니다.

https://github.com/Onther-Tech/l1-l2-deposit-withdrawal/blob/main/example.js

withdraw 트랜잭션이 실행된 후 처리를 대기하는 사이에 옵티미스틱 롤업은 검증 기간을 갖고 올바르지 않은 트랜잭션을 검증하기 위한 기간을 갖습니다. 이런 방식은 레이어2의 트랜잭션을 일단 레이어1에 등록시켜놓고, 올바르지 않은 트랜잭션을 검증하는 기간을 갖는 Fraud proof 방식입니다. 옵티미스틱 롤업에서 트랜잭션을 관리하는 방법과 검증에 대한 자세한 내용은 How does Optimism’s Rollup really work? 포스팅을 참고해 주시기 바랍니다.

테스트

Run optimistic rollup

ERC20 토큰의 예치/인출 테스트에 앞서 아래와 같이 테스트에서 필요한 환경을 구성합니다. 옵티미스틱 롤업의 빌드를 하지 않은 상태라면 이전 포스팅을 따라 환경 설치를 해주셔야 합니다.

$ cd optimism/ops
$ docker-compose up -d

Build repository

테스트에 필요한 저장소를 받고 빌드합니다. 이 저장소는 옵티미스틱 롤업에서의 ERC20 토큰 이동을 테스트하는 저장소로, 테스트에 사용할 컨트랙트 등을 빌드합니다.

$ cd ../
$ git clone https://github.com/Onther-Tech/l1-l2-deposit-withdrawal.git
$ cd l1-l2-deposit-withdrawal
$ yarn && yarn compile

Test ERC20 L1 <> L2

테스트를 실행합니다.

$ node ./example.js

Result

테스트가 정상적으로 종료되면 아래와 같은 결과를 보실 수 있습니다.

Deploying L1 ERC20...
Deploying L2 ERC20...
Deploying L1 ERC20 Gateway...
Initializing L2 ERC20...
Balance on L1: 1234
Balance on L2: 0
Approving tokens for ERC20 gateway...
Depositing tokens into L2 ERC20...
Waiting for deposit to be relayed to L2...
Balance on L1: 0
Balance on L2: 1234
Withdrawing tokens back to L1 ERC20...
Waiting for withdrawal to be relayed to L1...
Balance on L1: 1234
Balance on L2: 0

정리

옵티미스틱 롤업에서 ERC20 토큰을 사용하기 위해 예치/인출하는 방법에 대해 살펴보았습니다. 토큰의 L1 -> L2 이동은 사용자의 레이어1 토큰을 게이트웨이 컨트랙트로 전송하고 같은 양의 토큰을 레이어2에서 발행 받는 방법으로 처리되고, L1 <- L2 이동은 사용자의 레이어2 토큰을 소각하고 같은 양의 토큰을 레이어1의 게이트웨이 컨트랙트로부터 받아 처리합니다. 예치와 인출을 다루는 것은 deposit과 withdraw 함수를 호출하는 것과 레이어2의 처리를 대기하는 것이 추가된 정도로, 크게 복잡하지 않다는 것을 확인했습니다.

참조

Optimism Monorepo

Onther-Tech/l1-l2-deposit-withdrawal

ethereum-optimism/l1-l2-deposit-withdrawal

--

--