브라우니 사용법에 대해 궁금한 점이 있으시다면, Ethereum StackExchange에 질문하시거나 Gitter에 참여해 주시기 바랍니다.
새 프로젝트 만들기
주요 문서: 새 프로젝트 만들기
브라우니를 사용하기 위한 첫 번째 단계는 새 프로젝트를 초기화하는 것입니다. 이렇게 하려면 빈 폴더를 만든 다음 입력합니다:
$ brownie init
프로젝트를 구축하기 위한 간단한 템플릿인 "브라우니 믹스"를 초기화할 수도 있습니다. 이 문서의 예시에서는 매우 기본적인 ERC-20 구현인 토큰 믹스를 사용하겠습니다:
$ brownie bake token
이렇게 하면 token/
하위 디렉터리가 생성되고 그 안에 템플릿 프로젝트가 다운로드됩니다.
프로젝트 살펴보기
주요 문서: 프로젝트 구조
각 브라우니 프로젝트는 다음과 같은 구조를 사용합니다:
contracts/
: 컨트랙트 소스
interfaces/
: 인터페이스 소스
scripts/
: 배포 및 상호작용을 위한 스크립트
tests/
: 프로젝트 테스트용 스크립트
다음 디렉터리도 생성되어 브라우니 내부에서 프로젝트 관리를 위해 사용됩니다. 이 폴더 내의 파일을 편집하거나 삭제해서는 안 됩니다.
build/
: 컴파일러 아티팩트 및 단위 테스트 결과와 같은 프로젝트 데이터
reports/
: GUI에서 사용하기 위한 JSON 보고서 파일
컨트랙트 컴파일하기
주요 문서: 컨트랙트 컴파일하기
프로젝트를 컴파일합니다:
$ brownie compile
다음과 같은 출력이 표시됩니다:
Brownie - Python development framework for Ethereum
Compiling contracts...
Optimizer: Enabled Runs: 200
- Token.sol...
- SafeMath.sol...
Brownie project has been compiled at token/build/contracts
구성 파일을 편집하여 컴파일러 버전 및 최적화 설정을 변경할 수 있습니다.
핵심 기능
콘솔은 로컬이 아닌 체인에 배포된 컨트랙트와 직접 상호 작용하거나 개발 과정에서 빠르게 테스트하고 싶을 때 유용합니다. 또한 브라우니의 기능에 익숙해지기 위한 좋은 출발점이기도 합니다.
콘솔은 일반 파이썬 인터프리터와 매우 유사하게 느껴집니다. 프로젝트 디렉토리 내부에서 입력하여 로드합니다:
$ brownie console
브라우니는 컨트랙트를 컴파일하고 로컬 RPC 클라이언트를 시작하며 명령 프롬프트를 제공합니다. 여기에서 브라우니 API가 제공하는 모든 기능을 사용하여 네트워크와 상호작용할 수 있습니다.
계정
주요 문서: 계정으로 작업하기
로컬 계정에 대한 액세스는 트랜잭션을 할 수 있는 Account
개체가 포함된 목록과 같은 객체인 accounts
를 통해 이루어집니다.
다음은 잔액을 확인하고 이더를 이체하는 예시입니다:
>>> accounts[0]
<Account object '0xC0BcE0346d4d93e30008A1FE83a2Cf8CfB9Ed301'>
>>> accounts[1].balance()
100000000000000000000
>>> accounts[0].transfer(accounts[1], "10 ether")
Transaction sent: 0x124ba3f9f9e5a8c5e7e559390bebf8dfca998ef32130ddd114b7858f255f6369
Transaction confirmed - block: 1 gas spent: 21000
<Transaction object '0x124ba3f9f9e5a8c5e7e559390bebf8dfca998ef32130ddd114b7858f255f6369'>
>>> accounts[1].balance()
110000000000000000000
컨트랙트
주요 문서: 컨트랙트로 작업하기
브라우니는 프로젝트에서 배포 가능한 각 컨트랙트에 대해 ContractContainer
객체를 제공합니다. 이는 새 컨트랙트를 배포하는 데 사용되는 list-like 객체입니다.
>>> Token
[]
>>> Token.deploy
<ContractConstructor object 'Token.constructor(string _symbol, string _name, uint256 _decimals, uint256 _totalSupply)'>
>>> t = Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[1]})
Transaction sent: 0x2e3cab83342edda14141714ced002e1326ecd8cded4cd0cf14b2f037b690b976
Transaction confirmed - block: 1 gas spent: 594186
Contract deployed at: 0x5419710735c2D6c3e4db8F30EF2d361F70a4b380
<Token Contract object '0x5419710735c2D6c3e4db8F30EF2d361F70a4b380'>
>>> t
<Token Contract object '0x5419710735c2D6c3e4db8F30EF2d361F70a4b380'>
컨트랙트가 배포되면 해당 컨트랙와 상호 작용하는 데 사용할 수 있는 Contract
개체가 반환됩니다. 이 객체는 ContractContainer
에도 추가됩니다.
Contract
객체에는 호출과 트랜잭션을 수행하기 위한 클래스 메서드가 포함되어 있습니다. 이 예에서는 토큰 잔액을 확인하고 토큰을 전송합니다:
>>> t
<Token Contract object '0x5419710735c2D6c3e4db8F30EF2d361F70a4b380'>
>>> t.balanceOf(accounts[1])
1000000000000000000000
>>> t.transfer
<ContractTx object 'transfer(address _to, uint256 _value)'>
>>> t.transfer(accounts[2], 1e20, {'from': accounts[1]})
Transaction sent: 0xcd98225a77409b8d81023a3a4be15832e763cd09c74ff431236bfc6d56a74532
Transaction confirmed - block: 2 gas spent: 51241
<Transaction object '0xcd98225a77409b8d81023a3a4be15832e763cd09c74ff431236bfc6d56a74532'>
>>> t.balanceOf(accounts[1])
900000000000000000000
>>> t.balanceOf(accounts[2])
100000000000000000000
계약 소스에 NatSpec 문서가 포함된 경우 ContractCall.info
메서드를 통해 해당 문서를 볼 수 있습니다:
>>> t.transfer.info()
transfer(address _to, uint256 _value)
@dev transfer tokens for a specified address
@param _to The address to transfer to.
@param _value The amount to be transferred.
@return Success boolean
트랜잭션
주요 문서: 트랜잭션 검사 및 디버깅하기
TransactionReceipt
객체에는 트랜잭션에 대한 모든 관련 정보와 디버깅에 도움이 되는 다양한 메서드가 포함되어 있습니다.
>>> tx = Token[0].transfer(accounts[2], 1e18, {'from': accounts[1]})
Transaction sent: 0x0d96e8ceb555616fca79dd9d07971a9148295777bb767f9aa5b34ede483c9753
Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 1
Token.transfer confirmed - block: 2 gas used: 51019 (33.78%)
>>> tx
<Transaction object '0x0d96e8ceb555616fca79dd9d07971a9148295777bb767f9aa5b34ede483c9753'>
TransactionReceipt.events
를 사용하여 발생한 이벤트를 검사합니다:
>>> len(tx.events)
1
>>> 'Transfer' in tx.events
True
>>> tx.events['Transfer']
OrderedDict([('from', '0x33A4622B82D4c04a53e170c638B944ce27cffce3'), ('to', '0x0063046686E46Dc6F15918b61AE2B121458534a5'), ('value', 1000000000000000000)])
트랜잭션 추적을 검사합니다:
>>> tx.call_trace()
Call trace for '0x0d96e8ceb555616fca79dd9d07971a9148295777bb767f9aa5b34ede483c9753':
Token.transfer 0:244 (0x4A32104371b05837F2A36dF6D850FA33A92a178D)
├─Token.transfer 72:226
├─SafeMath.sub 100:114
└─SafeMath.add 149:165
트랜잭션이 반환된 이유에 대한 정보를 확인합니다:
>>> tx = Token[0].transfer(accounts[1], 1e18, {'from': accounts[3]})
Transaction sent: 0x5ff198f3a52250856f24792889b5251c120a9ecfb8d224549cb97c465c04262a
Token.transfer confirmed (Insufficient balance) - block: 2 gas used: 23858 (19.26%)
<Transaction object '0x5ff198f3a52250856f24792889b5251c120a9ecfb8d224549cb97c465c04262a'>
>>> tx.traceback()
Traceback for '0x5ff198f3a52250856f24792889b5251c120a9ecfb8d224549cb97c465c04262a':
Trace step 79, program counter 1167:
File "contracts/Token.sol", line 100, in Token.transfer:
_transfer(msg.sender, _to, _value);
Trace step 138, program counter 1340:
File "contracts/Token.sol", line 87, in Token._transfer:
require(balances[_from] >= _value, "Insufficient balance");
스크립트 작성
주요 문서: 스크립트 작성하기
스크립트를 작성하여 컨트랙트 배포 및 상호작용을 자동화할 수 있습니다. 스크립트 시작 부분에 from brownie import *
를 넣으면 콘솔에서와 동일하게 개체에 액세스할 수 있습니다.
스크립트에서 main
기능을 실행하려면 scripts/
폴더에 저장하고 입력합니다:
$ brownie run [script name]
토큰 프로젝트 내에서 배포에 사용되는 예제 스크립트는 scripts/token.py에서 찾을 수 있습니다:
from brownie import *
def main():
Token.deploy("Test Token", "TEST", 18, 1e23, {'from': accounts[0]})
프로젝트 테스트하기
주요 문서: 단위 테스트 작성하기
브라우니는 컨트랙트 테스트에 pytest
프레임워크를 사용합니다.
테스트는 tests/
폴더에 저장해야 합니다. 전체 제품군을 실행하려면:
$ brownie test
픽스처
브라우니는 프로젝트와 상호 작용하고 테스트에 도움이 되는 pytest
픽스처를 제공합니다. 픽스처를 사용하려면 테스트 함수의 입력에 같은 이름의 인수를 추가하세요.
다음은 브라우니에서 자동으로 생성된 픽스처를 사용한 테스트 함수 예제입니다:
def test_transfer(Token, accounts):
token = Token.deploy("Test Token", "TST", 18, 1e20, {'from': accounts[0]})
assert token.totalSupply() == 1e20
token.transfer(accounts[1], 1e19, {'from': accounts[0]})
assert token.balanceOf(accounts[1]) == 1e19
assert token.balanceOf(accounts[0]) == 9e19
전체 픽스처 목록은 Pytest Fixtures 섹션을 참조하세요.
취소된 트랜잭션 처리하기
취소된 트랜잭션은 VirtualMachineError
예외를 발생시킵니다. 이에 대한 어설션을 작성하려면 brownie.reverts
를 컨텍스트 관리자로 사용할 수 있으며, 이는 pytest.raises
와 매우 유사하게 작동합니다:
import brownie
def test_transfer_reverts(accounts, Token):
token = accounts[0].deploy(Token, "Test Token", "TST", 18, 1e23)
with brownie.reverts():
token.transfer(accounts[1], 1e24, {'from': accounts[0]})
선택적으로 문자열을 인자로 포함할 수 있습니다. 인수가 지정되면 트랜잭션에서 반환된 오류 문자열과 일치해야 테스트를 통과할 수 있습니다.
import brownie
def test_transfer_reverts(accounts, Token):
token = accounts[0].deploy(Token, "Test Token", "TST", 18, 1e23)
with brownie.reverts("Insufficient Balance"):
token.transfer(accounts[1], 1e24, {'from': accounts[0]})
테스트 격리
테스트 격리는 module_isolation
및 fn_isolation
fixtures를 통해 처리됩니다:
module_isolation
은 모듈 완료 전후에 로컬 체인을 재설정하여 이 모듈에 대한 깨끗한 환경을 보장하고 이 모듈의 결과가 후속 모듈에 영향을 미치지 않도록 합니다.
- 또한
fn_isolation
은 각 테스트를 실행하기 전에 체인의 스냅샷을 생성하고 테스트가 완료되면 해당 체인을 되돌립니다. 이를 통해 각 테스트에 대해 공통 상태를 정의하여 반복적인 트랜잭션을 줄일 수 있습니다.
이 예시에서는 격리 및 공유 설정 픽스처를 사용합니다. token
픽스처는 세션 범위를 사용하기 때문에 컨트랙트를 배포하는 트랜잭션은 한 번만 실행됩니다.
import pytest
from brownie import accounts
@pytest.fixture(scope="module")
def token(Token):
yield Token.deploy("Test Token", "TST", 18, 1e20, {'from': accounts[0]})
def test_transferFrom(fn_isolation, token):
token.approve(accounts[1], 6e18, {'from': accounts[0]})
token.transferFrom(accounts[0], accounts[2], 5e18, {'from': accounts[1]})
assert token.balanceOf(accounts[2]) == 5e18
assert token.balanceOf(accounts[0]) == 9.5e19
assert token.allowance(accounts[0], accounts[1]) == 1e18
def test_balance_allowance(fn_isolation, token):
assert token.balanceOf(accounts[0]) == 1e20
assert token.allowance(accounts[0], accounts[1]) == 0
'블록체인 (Block Chain) > 이더리움' 카테고리의 다른 글
[브라우니 (Brownie)] 3. 새 프로젝트 만들기 (0) | 2023.06.16 |
---|---|
[브라우니(Brownie)] 2. 브라우니 설치하기 (0) | 2023.06.16 |
브라우니 (Brownie) (0) | 2023.06.16 |
[솔리디티 입문] 9. 조건문 (1) | 2023.06.16 |
[솔리디티 입문] 8. 이벤트 (0) | 2023.06.16 |