본문으로 건너뛰기

계정 등록

이전 튜토리얼에서 토큰의 초기 순환 공급을 발행하는 방법과 이벤트 표준에 따라 이벤트를 기록하는 방법을 살펴보았습니다. 그런 다음 컨트랙트를 배포하고 지갑 잔고에서 FT를 확인했습니다. 이 튜토리얼에서는 스토리지 관리 표준에 대해 알아보고, FT 컨트랙트에 계정을 등록하여 악의적인 사람이 컨트랙트에서 모든 자금을 고갈시키는 것을 방지하는 방법에 대해 알아봅니다.

소개

새로운 사람이 대체 가능한 토큰(FT)을 받을 때마다 컨트랙트의 accounts 조회 맵에 추가됩니다. 이렇게 하면 컨트랙트에 바이트가 추가됩니다. 모든 사용자가 무료로 FT를 받을 수 있도록 만들면, 해당 시스템이 쉽게 악용될 수 있습니다. 사용자는 기본적으로 소량의 FT를 많은 계정에 전송하여, 모든 컨트랙트의 자금을 "탈취"할 수 있습니다. 이러한 이유로, 사용자가 저장하는 정보와 컨트랙트에 사용하는 바이트에 대해 요금을 청구할 수 있습니다. 그러나 사용자에게 청구하는 이 방법은 모든 컨트랙트에서 작동하도록 표준화되어야 합니다.

스토리지 관리 표준 입력

스토리지 관리 표준

스토리지 관리 표준은 컨트랙트에서 스토리지에 대해 사용자에게 요금을 청구하는 방식을 제어하는 ​​일련의 규칙입니다. 표준을 구현하는 모든 컨트랙트가 서로 상호 운용될 수 있도록 하는 함수와 동작을 설명합니다. 구현해야 하는 3가지 함수는 다음과 같습니다.

  • storage_deposit - 사용자가 일정량의 스토리지를 컨트랙트에 예치할 수 있습니다. 사용자가 초과된 금액을 예지하면, 초과한 $NEAR는 환불됩니다.
  • storage_balance_of - 주어진 사용자가 지불한 스토리지를 쿼리합니다.
  • storage_balance_bounds - 주어진 컨트랙트와 상호 작용하는 데 필요한 최소 및 최대 스토리지 양을 쿼리합니다.

이러한 함수 개요를 통해, 흐름이 다음과 같다고 가정할 수 있습니다.

  1. 사용자가 컨트랙트에 존재하지 않는 경우, 사용하는 바이트를 충당하기 위해 일부 스토리지를 예치해야 합니다.
  2. 사용자가 storage_deposit 함수를 통해 $NEAR를 입금하면, 컨트랙트와 자유롭게 상호 작용할 수 있습니다.

보증금이 얼마인지 스스로에게 물어볼 수 있습니다. 이 정보를 얻는 방법에는 두 가지가 있습니다.

  • 모든 개별 사용자가 storage_deposit 함수에서 차지하는 바이트를 맵에 삽입하고, 바이트를 측정한 다음, 나중에 accounts 맵에서 제거하여 동적으로 계산합니다.
  • 컨트랙트를 초기화할 때, 가능한 한 가장 큰 계정 ID를 삽입하기 위한 바이트를 계산하고, 모든 사용자에게 동일한 금액을 청구합니다.

단순화를 위해 두 번째 방법을 가정하겠습니다.

컨트랙트 수정

이 "가장 긴 계정 ID의 바이트"는 storage_deposit 함수 실행 중에 값을 가져오고, 사용자가 충분한 $NEAR를 첨부할 수 있도록 컨트랙트 상태에 저장되어야 합니다. src/lib.rs 파일을 열고 Contract 구조체에 다음 코드를 추가합니다. 지금 가입하는 경우 3.initial-supply 폴더에서 이 튜토리얼의 뼈대 코드를 찾을 수 있습니다.

4.storage/src/lib.rs
loading...

You'll now need a way to calculate this amount which will be done in the initialization function. Move to the src/internal.rs file and add the following private function measure_bytes_for_longest_account_id which will add the longest possible account ID and remove it while measuring how many bytes the operation took. It will then set the bytes_for_longest_account_id field to the result.

4.storage/src/internal.rs
loading...

또한 스토리지 비용을 지불한 후 계정을 "등록"하는 함수를 만들고 싶을 것입니다. 이렇게 하려면 잔고가 0인 상태로 accounts 맵에 삽입하기만 하면 됩니다. 이렇게 하면 현재 맵에 있는 모든 계정이 "등록"된 것으로 간주되고, 스토리지 비용을 지불했음을 알 수 있습니다. FT를 받으려는 계정은 잔고가 0 이상인 상태로 맵에 있어야 합니다. 그렇지 않은 경우 컨트랙트 실행이 취소됩니다.

4.storage/src/internal.rs
loading...

사용자가 아직 존재하지 않는 경우, 사용자 정의 메시지로 패닉하는 함수도 만들어 봅시다.

4.storage/src/internal.rs
loading...

이제 internal_deposit 함수를 호출할 때, 사용자 잔액이 아직 존재하지 않는 경우 다음을 통해 기본값 0으로 설정하는 대신:

let balance = self.accounts.get(&account_id).unwrap_or(0);

You can replace it with the following:

4.storage/src/internal.rs
loading...

이 작업이 완료되면, internal.rs는 다음과 같이 표시됩니다.

use near_sdk::{require};

use crate::*;

impl Contract {
pub(crate) fn internal_unwrap_balance_of(&self, account_id: &AccountId) -> Balance {
...
}

pub(crate) fn internal_deposit(&mut self, account_id: &AccountId, amount: Balance) {
...
}

pub(crate) fn internal_register_account(&mut self, account_id: &AccountId) {
...
}

pub(crate) fn measure_bytes_for_longest_account_id(&mut self) {
...
}
}

이것으로 해결해야 할 문제는 하나뿐입니다. 컨트랙트를 초기화할 때, 구현이 취소될 것입니다. 이는 당신이 internal_deposit를 호출하였고, 소유자가 아직 잔액이 없기 때문입니다. 이를 해결하기 위해, FT를 계정에 입금하기 전에 소유자를 등록하도록 초기화 함수를 수정해 보겠습니다. 또한 이 함수에서 등록을 위한 바이트를 측정해야 합니다.

4.storage/src/lib.rs
loading...

스토리지 표준 구현

이 작업이 완료되면 이제 스토리지 관리 표준을 구현할 준비가 된 것입니다. 구현할 세 가지 함수를 기억한다면 각각 핵심 함수로 분류하고, 진행 방법을 결정할 수 있습니다.

  • storage_balance_bounds - 주어진 컨트랙트와 상호 작용하는 데 필요한 최소 및 최대 스토리지 양을 쿼리합니다.

대체 가능한 토큰 컨트랙트를 생성하고, 스토리지 가격은 변경되지 않으므로(바이트당 $NEAR 비용이 변경되지 않는 한) 최소 및 최대 스토리지 비용은 동일해야 합니다. 이 값은 바이트당 현재 $NEAR 가격을 곱한 함수 measure_bytes_for_longest_account_id에서 계산한 가장 긴 계정 ID의 바이트 양과 같아야 합니다. 시작하려면 src/storage.rs 파일로 이동하세요.

4.storage/src/storage.rs
loading...
  • storage_balance_of - 지정된 사용자가 지불한 스토리지에 대한 쿼리입니다.

앞서 언급했듯이, 누군가가 accounts 맵에 있는지 확인하여 스토리지 비용을 지불했는지 알 수 있습니다. 그렇다면 storage_balance_bounds에서 반환한 금액을 지불했음을 알 수 있습니다.

4.storage/src/storage.rs
loading...
  • storage_deposit - 사용자가 일정량의 스토리지를 컨트랙트에 예치할 수 있습니다. 사용자가 초과된 보증금을 예치하면, 초과 $NEAR 액수에 대해 환불을 진행합니다

이 로직을 구현하기 위해서는 먼저 첨부된 보증금을 받아야 합니다. 또한 사용자가 이미 스토리지 비용을 지불하지 않았는지 확인해야 합니다(예: 사용자가 accounts 맵에 있음). 만약 지불했다면, 발신자가 호출에 첨부한 $NEAR에 대해 발신자에게 환불하면 됩니다.

사용자가 아직 등록되지 않은 경우, storage_balance_bounds를 호출하여 스토리지 비용을 확인하고, 해당 금액을 충당할 수 있을 만큼의 보증금을 첨부했는지 확인해야 합니다. 이 작업이 완료되면 등록하고 초과된 $NEAR를 환불할 수 있습니다.

4.storage/src/storage.rs
loading...

이 작업이 완료되면 컨트랙트를 빌드하고 배포할 준비가 된 것입니다!

컨트랙트 배포

현재 보유하고 있는 컨트랙트는 이미 초기화되었으므로, 하위 계정을 생성하여 다시 배포해 봅시다.

하위 계정(sub-account) 생성

다음 명령을 실행하여 초기 잔액이 25 NEAR인 하위 계정 storage을 만듭니다.

near create-account storage.$FT_CONTRACT_ID --masterAccount $FT_CONTRACT_ID --initialBalance 25

다음으로 개발을 쉽게 하기 위해 환경 변수를 내보낼 수 있습니다.

export STORAGE_FT_CONTRACT_ID=storage.$FT_CONTRACT_ID

빌드 스크립트를 사용하여 이전 튜토리얼에서와 같이 컨트랙트 배포를 빌드합니다.

cd 3.initial-supply && ./build.sh && cd .. && near deploy $STORAGE_FT_CONTRACT_ID out/contract.wasm

초기화

이제 컨트랙트가 배포되었으므로 이를 초기화하고 총 공급량을 생성할 차례입니다. 다시 한 번 초기 공급량을 1000gtNEAR로 만들어 봅시다.

near call $STORAGE_FT_CONTRACT_ID new_default_meta '{"owner_id": "'$STORAGE_FT_CONTRACT_ID'", "total_supply": "1000000000000000000000000000"}' --accountId $STORAGE_FT_CONTRACT_ID

이제 소유자가 지불한 스토리지를 쿼리하면 해당 스토리지가 등록된 것을 볼 수 있습니다!

near view $STORAGE_FT_CONTRACT_ID storage_balance_of '{"account_id": "'$STORAGE_FT_CONTRACT_ID'"}'

이는 구조체를 반환해야 합니다. 총 금액은 대략 등록에 0.00125 $NEAR가 들며, 사용자는 등록 비용을 지불하는 데 모두 사용했기 때문에 사용 가능한 $NEAR가 0입니다.

{ total: '1250000000000000000000', available: '0' }

컨트랙트와 상호 작용하는 데 필요한 스토리지 잔액을 쿼리할 수도 있습니다.

near view $STORAGE_FT_CONTRACT_ID storage_balance_bounds

최소값이 최대값과 같은 위의 storage_balance_of 쿼리와 동일한 금액을 반환하는 것을 볼 수 있습니다.

{ min: '1250000000000000000000', max: '1250000000000000000000' }

결론

오늘 당신은 컨트랙트에 사용자를 등록하기 위한 로직을 살펴보고 만들었습니다. 이 로직은 스토리지 관리 표준을 준수하며 FT 컨트랙트를 작성할 때 요구 사항을 충족하도록 커스터마이징됩니다. 그런 다음 이러한 변경 사항을 빌드, 배포 및 테스트했습니다. 다음 튜토리얼에서는 FT를 다른 사용자에게 전송하는 방법에 대한 기본 사항을 살펴봅니다.

Was this page helpful?