컨트랙트 수정
이 섹션에서는 이전 섹션의 스마트 컨트랙트 뼈대를 수정합니다. 이 튜토리얼은 기본을 학습하기 위해, 다소 쓸데없는 방식으로 컨트랙트를 작성하는 것부터 시작합니다. 이를 확실하게 이해하면, 십자말풀이가 나올 때까지 이를 반복할 것입니다.
상수, 필드, 함수 추가
컨트랙트를 다음과 같이 수정하겠습니다.
loading...
We've done a few things here:
- 퍼즐 번호에 대한 상수를 설정합니다.
- 메인 구조체에
crossword_solution
필드를 추가했습니다. - 세 가지 함수를 구현했습니다. 하나는 보기 전용이고, 두 개는 상태를 변경할 수 있는 함수입니다.
- 로깅을 사용했습니다. 이는
near_sdk
크레이드에서env
를 가져오는 데에 필요합니다.
Before moving on, let's talk about these changes and how to think about them, beginning with the constant:
const PUZZLE_NUMBER: u8 = 1;
This is an in-memory value, meaning that when the smart contract is spun up and executed in the virtual machine, the value 1
is contained in the contract code. This differs from the next change, where a field is added to the struct containing the #[near_bindgen]
macro. The field crossword_solution
has the type of String
and, like any other fields added to this struct, the value will live in persistent storage. With NEAR, storage is "paid for" via the native NEAR token (Ⓝ). It is not "state rent" but storage staking, paid once, and returned when storage is deleted. This helps incentivize users to keep their state clean, allowing for a more healthy chain. Read more about storage staking here.
Let's now look at the three new functions:
pub fn get_puzzle_number(&self) -> u8 {
PUZZLE_NUMBER
}
As is covered in the mutability section of these docs, a "view-only" function will have open parenthesis around &self
while "change methods" or mutable functions will have &mut self
. In the function above, the PUZZLE_NUMBER
is returned. A user may call this method using the proper RPC endpoint without signing any transaction, since it's read-only. Think of it like a GET request, but using RPC endpoints that are documented here.
Mutable functions, on the other hand, require a signed transaction. The first example is a typical approach where the user supplies a parameter that's assigned to a field:
pub fn set_solution(&mut self, solution: String) {
self.crossword_solution = solution;
}
The next time the smart contract is called, the contract's field crossword_solution
will have changed.
The second example is provided for demonstration purposes:
pub fn guess_solution(&mut self, solution: String) {
if solution == self.crossword_solution {
env::log_str("You guessed right!")
} else {
env::log_str("Try again.")
}
}
Notice how we're not saving anything to state and only logging? Why does this need to be mutable?
Well, logging is ultimately captured inside blocks added to the blockchain. (More accurately, transactions are contained in chunks and chunks are contained in blocks. More info in the Nomicon spec.) So while it is not changing the data in the fields of the struct, it does cost some amount of gas to log, requiring a signed transaction by an account that pays for this gas.
구축 및 배포
Here's what we'll want to do:
컨트랙트 구축
The skeleton of the Rust contract we copied from the previous section has a build.sh
and build.bat
file for OS X / Linux and Windows, respectively. For more details on building contracts, please see this section.
Run the build script and expect to see the compiled Wasm file copied to the res
folder, instead of buried in the default folder structure Rust sets up.
./build.sh
하위 계정(subaccount) 생성
If you've followed from the previous section, you have NEAR CLI installed and a full-access key on your machine. While developing, it's a best practice to create a subaccount and deploy the contract to it. This makes it easy to quickly delete and recreate the subaccount, which wipes the state swiftly and starts from scratch. Let's use NEAR CLI to create a subaccount and fund with 1 NEAR:
near create-account crossword.friend.testnet --masterAccount friend.testnet --initialBalance 1
If you look again in your home directory's .near-credentials
, you'll see a new key for the subaccount with its own key pair. This new account is, for all intents and purposes, completely distinct from the account that created it. It might as well be alice.testnet
, as it has, by default, no special relationship with the parent account. To be clear, friend.testnet
cannot delete or deploy to crossword.friend.testnet
unless it's done in a single transaction using Batch Actions, which we'll cover later.
another.crossword.friend.testnet
, but this account must be created by crossword.friend.testnet
.friend.testnet
cannot create another.crossword.friend.testnet
because accounts may only create a subaccount that's "one level deeper."
See this visualization where two keys belonging to mike.near
are able to create new.mike.near
. We'll get into concepts around access keys later.
We won't get into top-level accounts or implicit accounts, but you may read more about that here.
Now that we have a key pair for our subaccount, we can deploy the contract to testnet and interact with it!