Modifying the contract
This section will modify the smart contract skeleton from the previous section. This tutorial will start by writing a contract in a somewhat useless way in order to learn the basics. Once we've got a solid understanding, we'll iterate until we have a crossword puzzle.
Add a const, a field, and functions
Let's modify the contract to be:
Loading...
We've done a few things here:
- Set a constant for the puzzle number.
- Added the field
crossword_solution
to our main struct. - Implemented three functions: one that's view-only and two that are mutable, meaning they have the ability to change state.
- Used logging, which required the import of
env
from ournear_sdk
crate.
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]
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 function 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.
Building and deploying
Here's what we'll want to do:
Build the contract
To build the contract, we'll be using cargo-near
.
Install cargo-near
first:
cargo install cargo-near
Run the following commands and expect to see the compiled Wasm file copied to the target/near
folder.
cd contract
cargo near build