본문으로 건너뛰기

Sui 개발자 치트 시트

Sui Network 개발자를 위한 모범 사례 빠른 참조이다.

Move

Sui에서 Move 스마트 계약을 작성할 때의 모범 사례이다.

일반

  • Move 개발 모범 사례는 Code Quality Checklist를 읽는다.
  • 일관된 이름 지정과 코딩 스타일을 위해 Move 모범 사례을 따른다.
  • 최대 크기를 1000개 이하로 알 수 있는 컬렉션에는 vector 기반 컬렉션(vector, VecSet, VecMap, PriorityQueue)을 사용한다.
    • 제3자 추가를 허용하는 컬렉션, 더 큰 컬렉션, 크기를 알 수 없는 컬렉션에는 동적 필드 기반 컬렉션(Table, Bag, ObjectBag, ObjectTable, LinkedTable)을 사용한다.
    • Move 객체의 최대 크기는 250KB이다. 더 큰 객체를 생성하려고 하면 트랜잭션이 중단된다. 객체에 계속 커지는 vector 기반 컬렉션이 없도록 한다.
  • 함수 f가 호출자로부터 예를 들어 SUI 결제를 받아야 한다면 fun f(payment: Coin<SUI>)가 아니라 fun f(payment: &mut Coin<SUI>, amount: u64)를 사용한다. 이 방식이 호출자에게 더 안전하다. 호출자는 정확히 얼마를 지불하는지 알 수 있으며, f가 올바른 금액을 꺼낼 것이라고 신뢰할 필요가 없다.

Composability

  • 지갑, 앱, explorer에서 객체가 표시되는 방식을 사용자 지정하려면 Sui Object Display를 사용한다.
  • self-transfer를 피한다. 가능하면 현재 함수에서 객체를 반환하여 PTB 구성하기에서 다른 명령에 사용할 수 있게 한다.

패키지 업그레이드

  • 패키지를 게시하기 전에 패키지 업그레이드를 읽는다.
  • 패키지는 불변이므로 게시된 모든 패키지는 영구적으로 호출될 수 있다. 이전 버전이 호출되지 않도록 객체 버전 관리를 사용한다.
  • 패키지 P1P2로 업그레이드해도 P1에 의존하는 다른 패키지와 클라이언트는 계속 P1을 사용한다. 이들은 P2로 자동 업데이트되지 않는다. 의존 패키지와 클라이언트 코드 모두 P2를 가리키도록 명시적으로 업데이트해야 한다.
  • 의존 패키지가 확장할 것으로 예상되는 패키지는 모든 버전이 따르는 표준(변하지 않는) 인터페이스를 제공하여 업그레이드 때마다 확장이 깨지지 않게 할 수 있다. Wormhole의 bridge 예제 전반에 걸친 message sending을 참고한다. 아웃바운드 메시지를 생성하는 확장 패키지는 Wormhole 패키지의 모든 버전에서 prepare_message를 사용해 MessageTicket을 생성할 수 있다. 메시지를 보내는 클라이언트 코드는 그 MessageTicket을 패키지 최신 버전의 publish_message에 전달해야 한다.
    • public 함수 signature는 삭제하거나 변경할 수 없지만 public(package) 함수는 그럴 수 있다. 영구적으로 유지될 라이브러리 함수를 노출하는 경우가 아니라면 public(package) 또는 private visibility를 적극적으로 사용한다.
    • struct 타입을 삭제하거나, 그 정의를 변경하거나, 업그레이드를 통해 새 abilities를 추가하는 것은 불가능하다. 새 타입은 영구적으로 유지되므로 신중하게 도입한다.

테스트

  • 다중 트랜잭션, 다중 송신자 테스트 시나리오를 모방하려면 sui::test_scenario 모듈을 사용한다.
  • 더 나은 테스트 오류 메시지를 위해 std::unit_testassert_eq! 매크로에는 assert_ref_eq! 모듈을 사용한다.
  • black-hole 함수 sui::test_utils에는 destroy 모듈을 사용한다.
  • std::debug를 통한 디버그 출력에는 print 모듈을 사용한다.
  • 테스트의 코드 커버리지 정보를 계산하려면 sui move test --coverage를 사용하고, 빨간색으로 강조된 미커버 line을 보려면 sui move coverage source --module <name>를 사용한다. 가능하다면 코드 커버리지를 100%까지 끌어올린다.

  • 최적의 성능과 데이터 일관성을 위해 같은 풀 노드에 쓰기와 읽기를 제출한다. TS SDK에서는 지갑의 signTransactionBlock API를 사용한 뒤, 지갑의 execute_transactionBlock API 대신 앱의 풀 노드에서 signAndExecuteTransactionBlock을 호출해 트랜잭션을 제출한다. 이렇게 하면 read-after-write consistency가 보장된다. 앱의 풀 노드에서 읽은 값은 체크포인트를 기다리지 않고 트랜잭션의 쓰기를 즉시 반영한다.
  • 풀 노드에서 과도하게 가져오기보다 자주 읽는 데이터를 위한 로컬 캐시를 구현한다.
  • 가능하면 새 스마트 계약 코드를 게시하는 대신 프로그래머블 트랜잭션 블록을 사용해 기존 온체인 기능을 조합한다. 프로그래머블 트랜잭션 블록은 대규모 배치와 heterogeneous composition을 허용하여 이미 낮은 가스 수수료를 더 낮춘다.
  • 가스 예산, 가스 가격, 코인 선택은 지갑에 맡긴다. 이렇게 하면 지갑에 더 많은 유연성이 생기고, 트랜잭션이 실패하지 않도록 드라이 런하는 책임도 지갑에 있다.

서명

  • 같은 소유 객체를 건드리는 두 동시 트랜잭션에는 절대 서명하지 않는다. 독립적인 소유 객체를 사용하거나, 다음 트랜잭션을 보내기 전에 한 트랜잭션이 끝날 때까지 기다린다. 이 규칙을 위반하면 클라이언트 equivocation이 발생할 수 있고, 그러면 두 트랜잭션에 관련된 소유 객체가 현재 에포크가 끝날 때까지 잠긴다.
  • 트랜잭션을 만드는 모든 sui client 명령(예: sui client publish, sui client call)은 서명할 base64 트랜잭션을 출력하기 위해 --serialize-output 플래그를 받을 수 있다.
  • Sui는 네이티브 다중 서명(multisig)을 포함하여 트랜잭션 서명을 위한 여러 서명 방식을 지원한다.

zkLogin

  • proving service는 가능한 한 드물게 호출한다. 사용자가 실제 트랜잭션을 수행하려고 할 때만 proving service를 호출하도록 앱 flow를 설계한다.
  • 임시 개인 키를 캐시하는 방식에 주의한다. 개인 키는 비밀번호처럼 매우 민감한 데이터로 취급한다. 만료되지 않은 임시 개인 키와 그에 대응하는 ZK 증명이 유출되면 공격자가 사용자의 자산을 훔칠 수 있다.