BTC
ETH
HTX
SOL
BNB
시장 동향 보기
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

DAOrayaki: Aptos & Move 실제 설명

DAOrayaki
特邀专栏作者
2022-09-21 03:19
이 기사는 약 13659자로, 전체를 읽는 데 약 20분이 소요됩니다
이 글은 주로 aptos cli와 aptos sdk의 동작 방법을 설명합니다.
AI 요약
펼치기
이 글은 주로 aptos cli와 aptos sdk의 동작 방법을 설명합니다.

DAOrayaki DAO 리서치 보너스 풀:

펀딩 주소: DAOrayaki.eth

투표 진행: DAO Reviewer 3/0 통과

연구 유형: Aptos,Layer1

작성자: FF@DoraFactory

이 글은 주로 aptos cli와 aptos sdk의 동작을 설명합니다.

Clone Aptos-core Repo

# Clone the Aptos repo.
git clone 
# cd into aptos-core directory.
cd aptos-core
# Run the scripts/dev_setup.sh Bash script as shown below. This will prepare your developer environment.
./scripts/dev_setup.sh
# Update your current shell environment.
source ~/.cargo/env
# Skip this step if you are not installing an Aptos node.
git checkout --track origin/devnet

Aptos 로컬 체인 시작

Using CLI to Run a Local Testnet | Aptos Docs[1]

  • 로컬 체인 시작

추신: 이 방법으로 시작된 로컬 체인과 데이터는 이 명령이 시작된 현재 폴더에 저장되며 .aptos/ 파일에 존재합니다.
aptos node run-local-testnet --with-faucet

성공적으로 시작됨:

Completed generating configuration:
        Log file: "/Users/greg/.aptos/testnet/validator.log"
        Test dir: "/Users/greg/.aptos/testnet"
        Aptos root key path: "/Users/greg/.aptos/testnet/mint.key"
        Waypoint: 0:74c9d14285ec19e6bd15fbe851007ea8b66efbd772f613c191aa78721cadac25
        ChainId: TESTING
        REST API endpoint: 0.0.0.0:8080
        FullNode network: /ip4/0.0.0.0/tcp/6181
Aptos is running, press ctrl-c to exit
Faucet is running.  Faucet endpoint: 0.0.0.0:8081

성공적으로 시작되면 나머지 API 및 수도꼭지 API의 주소를 묻는 메시지가 표시됩니다. 나중에 이 두 가지 정보를 aptos cli 환경에서 구성해야 합니다.

  • aptos cli 환경 구성

커맨드 라인을 통해 로컬 테스트 체인에 접근하고 호출하기 위해서는 위의 배포 정보에 따라 aptos cli에 대한 config를 구성해야 합니다.

PROFILE=local
aptos init --profile $PROFILE --rest-url  --faucet-url 

실행하는 동안 다음과 같은 출력이 표시됩니다. 비밀 키를 입력하도록 선택하거나 기본적으로 무작위로 생성할 수 있습니다.

Configuring for profile local
Using command line argument for rest URL 
Using command line argument for faucet URL 
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]

확인 후 계정이 생성되고 기본 토큰 수로 자금이 조달됩니다.

No key given, generating key...
Account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1 doesn't exist, creating it and funding it with 10000 coins
Aptos is now set up for account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1!  Run `aptos help` for more information about commands
{
  "Result": "Success"
}

이제부터는 명령에 --profile local을 추가하여 로컬 테스트넷에서 실행할 수 있습니다.

추신: 여기서 --profile은 k8s의 kube-config와 유사하며, 다른 프로필 환경을 설정하고 다른 네트워크를 제어할 수 있습니다.

프로필 구성은 실행기 주소, node-rest-api 및 faucet-api 정보를 설정합니다.

# cli가 제어하는 ​​모든 계정 나열
aptos account list
# 계정 자금:
aptos account fund --profile $PROFILE --account $PROFILE
# 새 자원 계정을 만듭니다.
aptos account create-resource-account --profile $PROFILE --seed 1
# 이동 계약 컴파일
aptos move compile --package-dir hello_blockchain
# 계약 배포
aptos move publish --package-dir hello_blockchain --named-addresses basecoin= --profile local
# 계약을 호출
aptos move run --function-id :::: --profile local
# 지정된 계정의 모듈/리소스 정보 나열
aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile local
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile local
# 계약 업그레이드
aptos move publish --upgrade-policy
    `arbitrary`, `compatible`,'불변'은 0, 1, 2에 해당합니다.
0은 검사를 수행하지 않고 대체 코드를 강제 적용합니다.
1 호환성 검사를 수행합니다(동일한 공개 함수는 기존 리소스의 메모리 레이아웃을 변경할 수 없음).
2 업그레이드 금지
게시될 때마다 체인의 정책을 이 게시의 정책과 비교합니다(기본값은 1).
이번 정책이 체인의 정책보다 작은 경우에만 계약 업그레이드가 허용됩니다.

간단한 이동 계약 배포

module MyCounterAddr::MyCounter {
    use std::signer;
    struct Counter has key, store {
        value:u64,
    }
    public fun init(account: &signer){
        move_to(account, Counter{value:0});
    }
    public fun incr(account: &signer) acquires Counter {
        let counter = borrow_global_mut(signer::address_of(account));
        counter.value = counter.value + 1;
    }
    public entry fun init_counter(account: signer){
        Self::init(&account)
    }
    public entry fun incr_counter(account: signer)  acquires Counter {
        Self::incr(&account)
    }
}

MyCounter 소스 코드 분석

모듈은 특정 주소로 게시된 패키지된 함수 및 구조 집합입니다. 스크립트를 사용할 때 게시된 모듈 또는 표준 라이브러리와 함께 실행해야 하며 표준 라이브러리 자체는 0x1 주소로 릴리스된 모듈 그룹입니다.

모듈 MyCounterAddr::MyCounter{ }는 MyCounterAddr 주소 아래에 있습니다(MyCounterAddr ="0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf") 모듈을 만듭니다.

std::signer를 사용하는 것은 표준 라이브러리 아래의 서명자 모듈을 사용하는 것입니다 서명자는 트랜잭션 발신자의 주소를 포함하는 리소스와 유사한 네이티브 복사 불가능한 유형입니다. 서명자 유형을 도입한 이유 중 하나는 발신자 권한이 필요한 기능과 그렇지 않은 기능을 명확히 하기 위해서였습니다. 따라서 함수는 사용자가 해당 리소스에 대한 무단 액세스를 얻도록 속일 수 없습니다. 자세한 내용은 소스 코드 [2]를 참조하십시오.

module std::signer {
    // Borrows the address of the signer
    // Conceptually, you can think of the `signer` as being a struct wrapper arround an
    // address
    // ```
    // struct signer has drop { addr: address }
    // ```
    // `borrow_address` borrows this inner field
    native public fun borrow_address(s: &signer): &address;
    // Copies the address of the signer
    public fun address_of(s: &signer): address {
        *borrow_address(s)
    }
    /// Return true only if `s` is a transaction signer. This is a spec function only available in spec.
    spec native fun is_txn_signer(s: signer): bool;
    /// Return true only if `a` is a transaction signer address. This is a spec function only available in spec.
    spec native fun is_txn_signer_addr(a: address): bool;
}

Struct & Abilities


struct Counter has key, store {
    value:u64,
}

struct를 사용하여 Counter라는 구조체를 정의함과 동시에 key와 store 두 종류의 limiter에 의해 수정된다.

Move의 유형 시스템은 유연하며 각 유형은 네 가지 능력을 정의할 수 있습니다.

유형의 값을 복사, 폐기 및 저장할 수 있는지 여부를 정의합니다.

네 가지 능력 한정자는 Copy, Drop, Store 및 Key입니다.

기능은 다음과 같습니다.

  • 복사 - 값을 복사할 수 있습니다.

  • 삭제 - 범위 끝에서 값을 삭제할 수 있습니다.

  • 키 - 값은 "글로벌 스토리지 작업"에서 키로 액세스할 수 있습니다.

  • 저장 - 값을 전역 상태로 저장할 수 있습니다.

키로 수정하여 여기에 저장한다는 것은 복사, 폐기 또는 재사용이 불가능하지만 안전하게 저장 및 전송할 수 있음을 의미합니다.

능력 구문

기본 유형 및 내장 유형의 기능은 미리 정의되고 변경할 수 없습니다. 정수, 벡터, 주소 및 부울 유형의 값은 본질적으로 복사, 삭제 및 저장 기능이 있습니다.

그러나 다음 구문에 따라 구조의 기능을 추가할 수 있습니다.

struct NAME has ABILITY [, ABILITY] { [FIELDS] }

간단한 라이브러리 예제:

module Library {
    // each ability has matching keyword
    // multiple abilities are listed with comma
    struct Book has store, copy, drop {
        year: u64
    }
    // single ability is also possible
    struct Storage has key {
        books: vector
    }
    // this one has no abilities
    struct Empty {}
}

리소스란?

리소스의 개념은 Move 백서에 자세히 설명되어 있습니다. 초기에는 resource라는 구조체 형태로 구현되었으나, 어빌리티가 도입된 이후에는 Key와 Store의 두 가지 어빌리티를 가진 구조체로 구현되었습니다. 리소스는 안전하게 디지털 자산을 나타낼 수 있으며 복사할 수 없고 폐기하거나 재사용할 수 없지만 안전하게 저장하고 전송할 수 있습니다.

자원의 정의

리소스는 키 및 저장 기능으로 제한된 구조체입니다.

module M {
    struct T has key, store {
        field: u8
    }
}리소스 제한

코드에서 리소스 유형에는 몇 가지 주요 제한 사항이 있습니다.

  • 리소스는 계정에 저장됩니다. 따라서 계정이 할당된 경우에만 존재하며 해당 계정을 통해서만 액세스할 수 있습니다.

  • 계정은 한 번에 특정 유형의 리소스 하나만 보유할 수 있습니다.

  • 리소스는 복사할 수 없으며 이에 대응하는 특수한 종류의 리소스는 제네릭에 관한 장에서 소개한 복사 가능 리소스와 다릅니다. (이것은 Rust의 소유권으로 추상화될 수 있습니다)

  • 리소스를 사용해야 합니다. 즉, 새로 생성된 리소스는 계정으로 이동해야 하며, 계정에서 이동된 리소스는 해체하거나 다른 계정으로 저장해야 합니다.

지금 막 사건

struct Counter has key, store {
    value:u64,
}

따라서 usdc와 같은 eth에서 새 자산을 발행해야 하는 경우 견고성과 차이점이 있습니다. 그런 다음 이 자산은 계약의 맵에 기록됩니다. 그러나 이동은 다릅니다. 자산은 사용자의 주소 아래에 리소스로 저장됩니다.

함수 정의

public fun init(account: &signer){
    move_to(account, Counter{value:0});
}
public fun incr(account: &signer) acquires Counter {
    let counter = borrow_global_mut(signer::address_of(account));
    counter.value = counter.value + 1;
}
public entry fun init_counter(account: signer){
    Self::init(&account)
}
public entry fun incr_counter(account: signer)  acquires Counter {
    Self::incr(&account)
}

정의 형식은 다음과 같습니다.

공개 fun 함수 이름(매개변수: 매개변수 유형) { }

이동 기능은 기본적으로 비공개이며 해당 기능이 정의된 모듈 내에서만 액세스할 수 있습니다. public 키워드는 함수의 기본 가시성을 변경하고 외부에서 액세스할 수 있도록 공개합니다.

init 메소드의 매개변수는 &signer로 계정이 합법적으로 서명된 후에만 호출할 수 있음을 의미하고 move_to는 move의 프리미티브로 카운터 리소스를 게시하고 서명자의 주소에 추가하는 데 사용됩니다. Move의 계정 모델에서 코드와 데이터는 계정 주소 아래에 저장됩니다.

다음은 일반적으로 사용되는 프리미티브 목록입니다.

  • move_to< T >(&signer,T): 서명자의 주소에 T 유형의 리소스를 게시하고 추가합니다.

  • move_from< T >(addr: address): T - 주소에서 T 유형의 리소스를 제거하고 이 리소스를 반환합니다.

  • borrow_global< T >(addr: address): &T - 주소에 있는 T 유형의 리소스에 대한 불변 참조를 반환합니다.

  • borrow_global_mut< T >(addr: address): &mut T - 주소에 있는 T 유형의 리소스에 대한 변경 가능한 참조를 반환합니다.

  • exists< T >(주소): bool: 주소 아래에 T 유형의 리소스가 있는지 확인합니다.

incr 메소드의 매개변수는 &signer이기도 합니다. 이는 메소드가 호출되기 전에 계정에 의해 합법적으로 서명되어야 함을 의미합니다.

함수의 반환 값 뒤에 있는 키워드 captures는 이 함수가 획득한 모든 리소스를 명시적으로 정의하는 데 사용됩니다.

Signer::address_of(account) 서명자로부터 주소 가져오기

위에서 언급했듯이, 변수는 주소 아래의 Counter 리소스에 차용한 다음 Counter 구조 아래의 값에 대해 +1 연산을 수행합니다.

다음 두 가지 방법은 스크립트 방법인데 위의 두 가지 기능과 다른 점은 무엇입니까?

  • public fun : 모든 모듈에서 메서드를 호출할 수 있습니다.

  • public(script) fun / public entry fun: 스크립트 함수는 모듈의 진입 방법으로, 로컬에서 스크립트를 실행하는 것처럼 콘솔을 통해 트랜잭션을 시작하여 메서드를 호출할 수 있음을 의미합니다.

Move의 다음 버전은 public(script) fun을 public entry fun으로 대체할 것입니다.

Self는 자체 모듈을 나타냅니다.

Aptos Cli를 사용하여 계약 컴파일, 배포 및 호출

# 새로운 테스트 환경 만들기
aptos init --profile devtest --rest-url  --faucet-url 
# 이동 계약 컴파일
aptos move compile --package-dir my-counter
# 계약 배포
# 예: aptos move publish --package-dir my-counter --named-addresses basecoin=0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664 --profile devtest
aptos move publish --package-dir my-counter --named-addresses basecoin= --profile devtest
# 계약을 호출
# 예를 들어:
# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::init_counter --profile devtest
# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::incr_counter --profile devtest
aptos move run --function-id :::: --profile devtest
# 지정된 계정의 모듈/리소스 정보 나열
aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile devtest
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile devtest

Aptos SDK는 Move 컨트랙트를 호출합니다.

계약을 컴파일한 후 sdk를 통해 계약을 호출할 수 있습니다.

SDK를 통해 계약을 배포하거나 SDK를 통해 이동 계약을 호출하도록 선택할 수 있습니다.

  • SDK를 통해 계약 배포

컴파일하면 이동 계약 폴더 아래에 build/ 폴더가 생성됩니다.

my-counter/build/Examples/bytecode_modules/MyCounter.mv 파일을 SDK 스크립트에 복사해야 합니다.

aptos move compile --package-dir my-counter
cp MyCounter.mv my-counter-sdk-demo/

  • 컨트랙트 관련 SDK 코드 배포

/** Publish a new module to the blockchain within the specified account */
export async function publishModule(accountFrom: AptosAccount, moduleHex: string): Promise {
  const moudleBundlePayload = new TxnBuilderTypes.TransactionPayloadModuleBundle(
    new TxnBuilderTypes.ModuleBundle([new TxnBuilderTypes.Module(new HexString(moduleHex).toUint8Array())]),
  );
  const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
    client.getAccount(accountFrom.address()),
    client.getChainId(),
  ]);
  const rawTxn = new TxnBuilderTypes.RawTransaction(
    TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
    BigInt(sequenceNumber),
    moudleBundlePayload,
    1000n,
    1n,
    BigInt(Math.floor(Date.now() / 1000) + 10),
    new TxnBuilderTypes.ChainId(chainId),
  );
  const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
  const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
  return transactionRes.hash;
}

  • SDK를 통해 거래 보내기

여기에서는 my-counter 계약의 init_counter 및 incr_counter를 예로 들어 보겠습니다.

이 두 가지 방법을 호출하는 두 가지 방법을 구성하여 클라이언트에서 init 및 incr 호출 기능을 실현하십시오.

async function initCounter(contractAddress: string, accountFrom: AptosAccount): Promise {
  const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
    TxnBuilderTypes.ScriptFunction.natural(
      `${contractAddress}::MyCounter`,// 계약 주소::계약명
      "init_counter",// 스크립트 함수 메서드
      [],
      [],
    ),
  );
  const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
    client.getAccount(accountFrom.address()),
    client.getChainId(),
  ]);
  const rawTxn = new TxnBuilderTypes.RawTransaction(
    TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
    BigInt(sequenceNumber),
    scriptFunctionPayload,
    1000n,
    1n,
    BigInt(Math.floor(Date.now() / 1000) + 10),
    new TxnBuilderTypes.ChainId(chainId),
  );
  const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
  const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
  return transactionRes.hash;
}
async function incrCounter(contractAddress: string, accountFrom: AptosAccount): Promise {
  const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
    TxnBuilderTypes.ScriptFunction.natural(
      `${contractAddress}::MyCounter`,
      "incr_counter",
      [],
      [],
    ),
  );
  const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
    client.getAccount(accountFrom.address()),
    client.getChainId(),
  ]);
  const rawTxn = new TxnBuilderTypes.RawTransaction(
    TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
    BigInt(sequenceNumber),
    scriptFunctionPayload,
    1000n,
    1n,
    BigInt(Math.floor(Date.now() / 1000) + 10),
    new TxnBuilderTypes.ChainId(chainId),
  );
  const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
  const transactionRes = await client.submitSignedBCSTransaction(bcsTxn);
  return transactionRes.hash;
}

  • SDK를 통해 계정의 리소스 정보를 가져옵니다.

리소스는 해당 리소스가 속한 계정 주소에 저장되며 계정 주소에 따라 관련 리소스 정보를 조회할 수 있습니다.

getCounter() 메서드는 실제로 my-counter 아래의 **Counter** 리소스를 가져오는 것입니다.


async function getCounter(contractAddress: string, accountAddress: MaybeHexString): Promise {
  try {
    const resource = await client.getAccountResource(
      accountAddress.toString(),
      `${contractAddress}::MyCounter::Counter`,
    );
    return (resource as any).data["value"];
  } catch (_) {
    return "";
  }
}

사실 이 효과는 sdk의 효과와 비슷합니다.

aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf

최종 주요 기능

async function main() {
  assert(process.argv.length == 3, "Expecting an argument that points to the helloblockchain module");
  const contractAddress = "0x173d51b1d50614b03d0c18ffcd958309042a9c0579b6b21fc9efeb48cdf6e0b0"; // 이전에 배포한 컨트랙트의 주소 지정
const bob = new AptosAccount(); // 테스트 주소 생성 Bob
  console.log("\n=== Addresses ===");
  console.log(`Bob: ${bob.address()}`);
  await faucetClient.fundAccount(bob.address(),5_000); // 5000 테스트 토큰을 Bob의 주소로 Airdrop
  console.log("\n=== Initial Balances ===");
  console.log(`Bob: ${await accountBalance(bob.address())}`);
  await new Promise((resolve) => {
    readline.question(
      "Update the module with Alice's address, build, copy to the provided path, and press enter.",
      () => {
        resolve();
        readline.close();
      },
    );
  });
  const modulePath = process.argv[2];
  const moduleHex = fs.readFileSync(modulePath).toString("hex");
  console.log('Init Counter Moudle.');
  let txHash = await initCounter(contractAddress,bob); // bob 아래에서 Counter 리소스를 초기화합니다. 이때 bob 아래의 Counter 값은 0입니다.
  await client.waitForTransaction(txHash);
  console.log("\n=== Testing Bob Get Counter Value ===");
  console.log(`Initial value: ${await getCounter(contractAddress, bob.address())}`);
  console.log('========== Incr Counter Value, 1th ==========');
  txHash = await incrCounter(contractAddress,bob); // bob은 incrCounter 메서드를 한 번 호출하고 이때 Counter는 1입니다.
  console.log(txHash);
  await client.waitForTransaction(txHash);
  await Sleep(100);
  console.log(`New value: ${await getCounter(contractAddress,bob.address())}`); // bob의 주소 아래 Counter 값을 구해서 출력한다.
  console.log('========== Incr Counter Value, 2th ==========');
  txHash = await incrCounter(contractAddress,bob); // bob은 incrCounter 메서드를 한 번 호출하고 Counter는 2입니다.
  console.log(txHash);
  await client.waitForTransaction(txHash);
  await Sleep(100);
  console.log(`New value: ${await getCounter(contractAddress,bob.address())}`); // bob의 주소 아래 Counter 값을 구해서 출력한다.
  console.log('========== Incr Counter Value, 3th ==========');
  txHash = await incrCounter(contractAddress,bob); // bob은 incrCounter 메서드를 한 번 호출하고 Counter는 3입니다.
  console.log(txHash);
  await client.waitForTransaction(txHash);
  await Sleep(100);
  console.log(`New value: ${await getCounter(contractAddress,bob.address())}`); // bob의 주소 아래 Counter 값을 구해서 출력한다.
}
if (require.main === module) {
  main().then((resp) => console.log(resp));
}

실행 효과

실행에 성공하면 SDK를 통해 임의로 생성된 계정이 Counter 리소스(Counter=0)로 초기화된 후 3번 증가하므로 Counter의 최종 값은 3이 됩니다.

image-20220831200516865

참조

참조

[1]Using CLI to Run a Local Testnet | Aptos Docs: https://aptos.dev/nodes/local-testnet/using-cli-to-run-a-local-testnet

[2] 소스 코드: https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/move-stdlib/sources/signer.move


Aptos
Odaily 공식 커뮤니티에 가입하세요