본문으로 건너뛰기

Foundry로 스마트 컨트랙트 배포하기

소개​

Foundry는 개발자가 Solidity 스크립트를 통해 명령줄에서 컨트랙트를 관리 및 컴파일하고, 테스트를 실행하고, 컨트랙트를 배포하고, 네트워크와 상호작용할 수 있도록 지원하는 Rust로 작성된 스마트 컨트랙트 개발 프레임워크입니다.

Foundry는 빠르고 모듈화된 스마트 컨트랙트 개발을 가능하게 하는 네 가지 주요 CLI 도구로 구성되어 있습니다:

  • Forge: Forge를 사용하여 스마트 컨트랙트를 배포, 테스트 및 컴파일할 수 있습니다.
  • Cast: 캐스트는 EVM 스마트 컨트랙트와의 상호작용을 간단하게 만들어줍니다. 여기에는 체인 데이터 가져오기, 트랜잭션 전송 등이 포함됩니다.
  • Anvil: 로컬 노드를 스핀업해야 하나요? Anvil은 Foundry에서 제공하는 로컬 노드 환경입니다.
  • Chisel: 빠르고 유용하며 자세한 Solidity REPL.

이 가이드에서는 다음을 수행합니다:

  • 간단한 Foundry 프로젝트를 생성합니다.
  • Foundry를 사용하여 스마트 컨트랙트 샘플을 컴파일하고 테스트합니다.
  • Foundry를 사용하여 스마트 컨트랙트를 클레이튼 Baobab 네트워크에 배포합니다.
  • Cast와 Anvil로 메인넷 포크 살펴보기.

사전 요구 사항​

이 튜토리얼을 따르기 위한 전제 조건은 다음과 같습니다:

  • 코드 편집기: VS-Code와 같은 소스 코드 편집기.
  • MetaMask: 컨트랙트를 배포하고, 트랜잭션에 서명하고, 컨트랙트와 상호 작용하는 데 사용됩니다.
  • RPC 엔드포인트: 지원되는 엔드포인트 공급자 중 하나에서 얻을 수 있습니다.
  • Faucet에서 KLAY 테스트: 충분한 KLAY로 계정에 자금을 충전합니다.
  • Rust 및 Foundry를 설치합니다.

개발 환경 설정하기​

Foundry 설치가 성공적으로 완료되었는지 확인하려면 아래 명령을 실행하세요:


forge -V

출력

Foundry를 성공적으로 설치했으면 이제 Foundry에서 사용할 수 있는 CLI 도구(forge, cast, anvil, chisel)에 액세스할 수 있습니다. 다음 단계에 따라 Foundry 프로젝트를 설정해 보겠습니다:

1단계: 새 프로젝트를 시작하려면 아래 명령을 실행합니다:


forge init foundry_example

2단계: 프로젝트 폴더로 이동합니다.


cd foundry_example
ls

Foundry 프로젝트를 초기화한 후, 현재 디렉터리에 다음이 포함되어야 합니다:

  • src: 스마트 컨트랙트의 기본 디렉터리입니다.
  • tests: 테스트를 위한 기본 디렉터리.
  • foundry.toml: 기본 프로젝트 구성 파일.
  • lib: 프로젝트 종속성을 위한 기본 디렉터리.
  • script: Solidity 스크립팅 파일의 기본 디렉터리.

스마트 컨트랙트 샘플​

이 섹션에서는 초기화된 Foundry 프로젝트에서 샘플 카운터 컨트랙트를 사용하겠습니다. src/ 폴더의 counter.sol 파일은 다음과 같은 모습이어야 합니다:


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}

코드 연습

이것이 여러분의 스마트 컨트랙트입니다. 1행은 Solidity 버전 0.8.13 이상을 사용함을 보여줍니다. 4줄부터 12줄까지는 스마트 컨트랙트 Counter가 생성됩니다. 이 컨트랙트는 setNumber 함수를 사용하여 새로운 숫자를 저장하고 increment 함수를 호출하여 숫자를 증가시킵니다.

스마트 컨트랙트 테스트​

Foundry를 사용하면 다른 스마트 컨트랙트 개발 프레임워크에서 JavaScript로 테스트를 작성하는 것과 달리 Solidity로 테스트를 작성할 수 있습니다. 초기화된 Foundry 프로젝트에서 test/Counter.t.sol은 Solidity로 작성된 테스트의 예시입니다. 코드는 다음과 같습니다:


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/Counter.sol";
contract CounterTest is Test {
Counter public counter;
function setUp() public {
counter = new Counter();
counter.setNumber(0);
}
function testIncrement() public {
counter.increment();
assertEq(counter.number(), 1);
}
function testSetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
}

위 코드는 위조 표준 라이브러리와 Counter.sol을 가져온 것을 보여줍니다.

위의 테스트는 다음을 확인합니다:

  • 숫자가 증가하고 있는가?
  • 숫자가 설정된 숫자와 같은가?

테스트가 정상적으로 작동하는지 확인하려면 다음 명령을 실행하세요:


forge test

출력

테스트 작성, 고급 테스트 및 기타 기능에 대해 자세히 알아보려면 Foundry 문서를 참조하세요.

컨트랙트 컴파일하기​

이 명령으로 컨트랙트를 컴파일합니다:


forge build

컨트랙트 배포하기​

Foundry를 사용하여 컨트랙트를 배포하려면, 컨트랙트를 배포할 계정의 RPC URL과 개인키를 제공해야 합니다. 클레이튼의 rpc 제공자 목록을 참고하여 rpc-url을 찾고, MetaMask를 사용하여 계정을 생성합니다.

1단계: 컨트랙트를 Klaytn Baobab 네트워크에 배포하려면, 아래 명령어를 실행합니다:


$ forge create --rpc-url <your_rpc_url> --private-key <your_private_key> src/Counter.sol:Counter

예시


forge create --rpc-url https://klaytn-baobab-rpc.allthatnode.com:8551/qtKkeUE8ZEPI2cs0OHloJ6seI4Wfy36N --private-key hhdhdhdhprivatekeyhdhdhdhud src/Counter.sol:Counter

경고: 개인 키 인수를 MetaMask의 개인 키로 바꾸세요. 개인키가 노출되지 않도록 각별히 주의하세요.

출력

Step 2: Open Klaytnscope to check if the counter contract deployed successfully.

3단계: 검색 필드에 트랜잭션 해시를 복사하여 붙여넣고 Enter 키를 누릅니다. 최근에 배포된 컨트랙트가 표시됩니다.

컨트랙트와 상호작용하기​

스마트 컨트랙트를 성공적으로 배포했다면, 이제 바로 함수를 호출하고 실행하고 싶을 것입니다. Cast를 사용하여 배포된 컨트랙트와 클레이튼 Baobab 네트워크에서 상호작용해 보겠습니다. 이번 장에서는 read-only 함수를 실행하기 위한 cast call과 write 함수를 실행하기 위한 cast send를 사용하는 방법을 배워보겠습니다.

A. Cast 호출: 컨트랙트에 저장된 번호를 가져오기 위해 number 함수를 호출하게 됩니다. 아래 명령어를 실행하여 실제로 작동하는지 확인해보세요.


cast call YOUR_CONTRACT_ADDRESS "number()" --rpc-url RPC-API-ENDPOINT-HERE

예시


cast call 0xe4d576c447733da7ca9197e88d34a74c3c865cff "number()" --rpc-url https://klaytn-baobab-rpc.allthatnode.com:8551/qtKkeUE8ZEPI2cs0OHloJ6seI4Wfy36N

출력

이 데이터는 16진수 형식으로 가져와야 합니다:


0x0000000000000000000000000000000000000000000000000000000000000000

그러나 원하는 결과를 얻으려면 캐스팅을 사용하여 위의 결과를 변환합니다. 이 경우 데이터가 숫자이므로 기본 10으로 변환하여 결과 0을 얻을 수 있습니다:


cast --to-base 0x0000000000000000000000000000000000000000000000000000000000000000 10

출력

B. cast send: 카운터 컨트랙트에서 setNumber 함수를 실행하는 것과 같은 트랜잭션에 서명하고 게시하려면 아래 명령을 실행합니다:


cast send --rpc-url=<RPC-URL> <CONTRACT-ADDRESS> “setNumber(uint256)” arg --private-key=<PRIVATE-KEY>

예시


cast send --rpc-url=https://klaytn-baobab-rpc.allthatnode.com:8551/qtKkeUE8ZEPI2cs0OHloJ6seI4Wfy36N 0xe4d576c447733da7ca9197e88d34a74c3c865cff "setNumber(uint256)" 10 --private-key=<private key>

출력

크로스체크 번호


cast call 0xe4d576c447733da7ca9197e88d34a74c3c865cff "number()" --rpc-url https://klaytn-baobab-rpc.allthatnode.com:8551/qtKkeUE8ZEPI2cs0OHloJ6seI4Wfy36N

출력

이 데이터는 16진수 형식으로 가져와야 합니다:


0x000000000000000000000000000000000000000000000000000000000000000a

그러나 원하는 결과를 얻으려면 캐스팅을 사용하여 위의 결과를 변환합니다. 이 경우 데이터가 숫자이므로 기본 10으로 변환하여 결과 10을 얻을 수 있습니다:


cast --to-base 0x000000000000000000000000000000000000000000000000000000000000000a 10

출력

Cast와 Anvil을 이용한 메인넷 포크​

Foundry를 사용하면 메인넷을 로컬 개발 네트워크(Anvil로 포크할 수 있습니다.) 또한 Cast를 사용하여 실제 네트워크에서 컨트랙트와 상호 작용하고 테스트할 수 있습니다.

시작하기​

이제 Foundry 프로젝트가 실행되었으므로 아래 명령을 실행하여 메인넷(Cypress)을 포크할 수 있습니다:


anvil --fork-url rpc-url

예제


anvil --fork-url https://archive-en.cypress.klaytn.net

출력

이 명령을 성공적으로 실행하면 터미널이 위 이미지와 같이 표시됩니다. 공개 키와 개인 키로 생성된 10개의 계정과 10,000개의 선지급 토큰이 있을 것입니다. 포크된 체인의 RPC 서버는 127.0.0.1:8545에서 수신 대기 중입니다.

네트워크를 포크했는지 확인하려면 최신 블록 번호를 조회하면 됩니다:


curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545

위 작업의 결과는 16진수를 10진수로 변환을 사용하여 변환할 수 있습니다. 네트워크를 포크한 시점의 최신 블록 번호를 얻어야 합니다. 이를 확인하려면 Klaytnscope에서 블록 번호를 상호 참조하세요.

따라하기​

이 섹션에서는 oUSDC 토큰을 보유한 사람으로부터 Anvil이 생성한 계정(0x70997970C51812dc3A010C7d01b50e0d17dc79C8 - Bob)으로 토큰을 전송하는 방법을 알아보세요.

oUSDC 전송하기

Klaytnscope로 이동하여 oUSDC 토큰 보유자를 검색합니다(여기). 임의의 계정을 선택하겠습니다. 이 예제에서는 0x8e61241e0525bd45cfc43dd7ba0229b422545bca를 사용하겠습니다.

컨트랙트와 계정을 환경 변수로 내보내 보겠습니다:


export BOB=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
export oUSDC=0x754288077d0ff82af7a5317c7cb8c444d421d103
export oUSDCHolder=0x8e61241e0525bd45cfc43dd7ba0229b422545bca

캐스팅 호출을 사용하여 밥의 잔액을 확인할 수 있습니다:


cast call $oUSDC \
"balanceOf(address)(uint256)" \
$BOB

출력

마찬가지로 캐스트 콜을 사용하여 oUSDC 보유자의 잔액을 확인할 수도 있습니다:


cast call $oUSDC \
"balanceOf(address)(uint256)" \
$oUSDCHolder

출력

캐스트 전송을 사용하여 행운의 사용자로부터 앨리스에게 토큰을 전송해 보겠습니다:


cast rpc anvil_impersonateAccount $oUSDCHolder
cast send $oUSDC \
--unlocked \
--from $oUSDCHolder\
"transfer(address,uint256)(bool)" \
$BOB \
1000000

출력

전송이 제대로 되었는지 확인해 보겠습니다:


cast call $oUSDC \
"balanceOf(address)(uint256)" \
$BOB

출력


cast call $oUSDC \
"balanceOf(address)(uint256)" \
$oUSDCHolder

출력

Foundry에 대한 더 자세한 가이드는 Foundry 문서를 참조하세요. Also, you can find the full implementation of the code for this guide on GitHub.

Make this page better