logo
banner
avatar
Nhabachoc Dũng
Thứ năm 2023
👹 Chiến thần Solidity đến đây 👹
# javascript
# html
# blockchain

Chao xìn mọi người lại là mình đây Nhabachoc KAD.

Bài viết này có dành cho bạn?

  • Chưa thoả mãn với những kiến thức ở bài Solidity cơ bản

  • Chưa hiểu rõ tại sao smart contract lại an toàn, minh bạch

Hành trang cần chuẩn bị:

  • Kiến thức về Solidity cơ bản

Cùng tắt đèn bật ý tưởng, gét go 🪄

Ethereum Virtual Machine - EVM

Máy ảo (VM) là một chương trình chạy trên hệ điều hành gốc. Nó mô phỏng một máy vật lý và cho phép nó chạy cùng một nền tảng trên nhiều hệ điều hành và kiến trúc phần cứng.

Có hai loại kiến trúc máy ảo chính:

  • Máy ảo register-based <các thanh ghi>

  • Máy ảo stack-based <mô hình ngăn xếp>

Hai điểm khác biệt chính giữa hai kiến trúc này là:

  1. Cách mỗi VM lưu trữ, truy xuất và sử dụng các đối số (ví dụ: toán hạng cho các phép toán số học) khi thực hiện các lệnh được xác định bởi ngôn ngữ trình biên dịch mã.

  2. Cấu trúc dữ liệu để lưu trữ các đối số này.

Máy ảo register-based

VM này sử dụng các thanh ghi để lưu trữ toán hạng. Cấu trúc dự liệu:

  • Key : Địa chỉ thanh ghi

  • Value : Giá trị muốn lưu trữ

Một số VM register-based nổi tiếng: Erlang VM, Lua VM, Dalvik VM

Máy ảo stack-based

VM này sử dụng stack <ngăn xếp> làm cấu trúc dữ liệu để lưu trữ các toán hạng. Các hoạt động được thực hiện bằng cách lấy dữ liệu từ stack, xử lý và đẩy kết quả trở lại stack. Chúng thường được thực hiện bằng cách sử dụng PUSHvà POP.

Sử dụng cơ chế LIFO có nghĩa là phần tử cuối cùng được đẩy vào stack luôn là phần tử đầu tiên được lấy ra khỏi stack.

Một số VM register-based nổi tiếng: Java VM

⇒ EVM là một máy ảo dựa trên Máy ảo stack-based.

Stack

Stack được sử dụng mọi lúc trong Solidity khi chúng ta gán giá trị cho biến. Có 5 quy tắc chính giúp bạn hiểu thực sự smart contract:

  • 2 quy tắc liên quan đến stack:

    • Có chi phí tương tác với dự liệu thấp nhất.

    • Stack chỉ có hiệu lực trong function scope.

  • 3 quy tắc về thao tác dữ liệu stack:

    • EVM sử dụng 4 mã lệnh cơ bản để thao tác với stack: PUSH, POP, SWAP, DUP.

    • Các đối số của opcode khác luôn lấy dữ liệu trên cùng trong stack.

    • Các opcode liên quan đến storage, memorycalldata tải các giá trị từ các vị trí dữ liệu này vào stack bằng cách sử dụng các opcodes liên quan đến từng vị trí dữ liệu.

Cách thức lưu trữ

  • Giá trị của 1 giá trị trong stack 256 bit.

  • Giới hạn về kích thước của stack là 1.024 giá trị <có trong tài liệu Ethereum Yellow Paper>.

  • Chỉ có thể truy cập các mục trên cùng thứ 16 trong stack.

Calldata

  • 4 byte đầu tiên tương ứng với bộ chọn của chữ ký hàm.

  • các byte còn lại tương ứng với các tham số đầu vào của hàm. Mỗi đối số đầu vào luôn dài 32 byte. Nếu loại của nó nhỏ hơn 32 byte, đối số sẽ được đệm.

Lưu ý : Các đối số đầu vào được đệm ở bên phải hoặc bên trái tùy thuộc vào loại của chúng (ví dụ: uintNhoặc addressđược đệm ở bên trái, trong khi bytesNđược đệm ở bên phải).

Dữ liệu được gửi cùng 1 messeage call. Nó đề cập đến các byte hex thô

Calldata được tạo từ các byte được sắp xếp liên tục giống như memory trái ngược với storage hoặc stack được tạo thành words (32 byte). Để đọc được calldata giống như memory: có thể tải 32 byte mỗi lần ( mloadvới memory và calldataload với calldata). Tuy nhiên, nó hoạt động khác với memory vì bạn không thể ghi vào nó.

Một cách để phân biệt calldata với memory là “ai tạo dữ liệu trong calldata” và “ai tạo dữ liệu trong bộ nhớ”:

Một cách tốt để suy nghĩ về sự khác biệt giữa calldata và memory và cách chúng nên được sử dụng là calldata được tạo bởi người gọi, trong khi memory được tạo bởi người được gọi.

Bây giờ chúng ta hãy xem các tính năng chính của calldata. Calldata là một vị trí dữ liệu nơi lưu giữ tham số dữ liệu của một giao dịch hoặc cuộc gọi.

  • Không thể sửa đổi - chỉ đọc: Bạn không thể sửa đổi hoặc thay đổi dữ liệu trong calldata. Nó không thể được ghi đè.

  • Hầu như không giới hạn về kích thước

  • Rất rẻ và tiết kiệm gas: Đọc + cấp phát byte trong calldata rất rẻ và tiết kiệm gas.

    • 4 gas cho byte0x00

    • 16 gas cho các byte khác không.

    • opcode CALLDATALOADchỉ tốn 3 gas để đọc một từ 32 byte

  • Không liên tục (sau khi giao dịch đã hoàn thành)

  • Cụ thể cho các giao dịch và cuộc gọi hợp đồng.

Storage

Như các bạn biết Storage là kho lưu trữ dữ liệu lâu dài và vô cùng tốn kém:

  • Khởi tạo vị trí lưu trữ: Bộ nhớ ban đầu là → muốn lưu trữ giá trị khác 0 tốn 20.000 gas

  • Chỉnh sửa giá trị tại 1 vị trí lưu trữ tốn 5.000 gas

  • Xoá giá trị tại vị trí lưu trữ sẽ hoàn lại 15.000 gas

Câu hỏi:

  • Đọc dữ liệu từ Storage có tốn gas không?

    Việc đọc dữ liệu từ Storage thường thấy thì không tốn gas nhưng chỉ miễn phí với tài khoản EOA, tính phí khi thao tác đọc dữ liệu là một phần của giao dịch có sửa đổi trạng thái trên hợp đồng.

  • Hợp đồng này có thể đọc dữ liệu từ Storage của hợp đồng khác không?

    Mặc định smart contract chỉ có thể đọc dữ liệu của chính nó trong môi trường thực thi. Một smart contract có thể đọc dữ liệu của hợp đồng khác khi có các chức năng đọc trong ABI.

Slot hay còn gọi là vị trí lưu trữ trong smart contract có đặc điểm:

  • Tổng cộng 2^256 slot nhớ để đọc/ghi

  • Mỗi slot có kích thước lên đến 256 bit (32 byte)

  • Value mặc định của mỗi slot là 0

Cách thức lưu trữ

Quy tắc chung khi lưu trữ:

  • Storage chỉ lưu các biến, không lưu constant

  • Các biến lần lượt được vào slot theo thứ tự lower-order (nghĩa là từ phải qua trái).

  • Nếu size của biến vượt quá size còn lại của slot, biến này sẽ được đưa qua slot mới.

  1. Fixed size

    Việc lưu trữ dữ liệu là ánh xạ key-value trong cơ sở dữ liệu:

    • Key : Biến đó đang nằm trong Slot nào, key chính là Slot

    • Value : Giá trị thực ở Slot này

  2. Dynamic Size

    1.1. Array

    Array sẽ luôn chiếm 1 slot mới kể cả khi slot liền kề còn trống

    Vẫn là theo dạng key-value nhưng slot đầu tiên dùng để chứa độ dài array gọi là A:

    • Key : Biến đó đang nằm trong Slot nào, key chính là Slot

    • Value : Length của array

    Chi tiết các phần tử trong array sẽ chiếm từ slot thứ tự: keccak256(<Slot A ở dạng chuỗi 64 ký tự hex>)

    1.2. Mappings

    Mapping sẽ luôn chiếm 1 slot mới

    Vẫn là theo dạng key-value nhưng slot đầu tiên không chứa giá trị gọi là M:

    • Key : Biến đó đang nằm trong Slot nào, key chính là Slot

    • Value : Không chứa giá trị

    Các phần từ trong mapping sẽ chiếm slot: keccak256(hex(<giá trị key của mapping ở dạng chuỗi 64 ký tự hex>) + hex(Slot M ở dạng chuỗi 64 ký tự hex))

  3. Bytes và String

    3.1. Bytes

    3.2. String

    String sẽ luôn chiếm 1 slot mới

    • String < 32 byte: string được lưu trữ kèm với độ dài của nó theo quy tắc: chuỗi được lưu từ bit trái qua phải (higher-order), còn độ dài thì lưu từ bit phải qua trái (lower-order) với giá trị length * 2.

    • String > 32 byte: thì main slot sẽ lưu trữ độ dài của chuỗi, tức length * 2 + 1. Còn giá trị thì theo thông thường sẽ được lưu trữ tại keccak256(S), với S là vị trí của main slot.

Memory

Layout của memory

hoạt động dựa trên các slot 256 bit - 32 byte

Không gian riêng

  • 0x000x20 : Dành cho hàm băm

  • 0x400x50: con trỏ bộ nhớ trống

  • 0x60: Bằng 0 vĩnh viễn và được sử dụng làm giá trị ban đầu cho các mạng bộ nhớ động trống

Giới hạn bộ nhớ tối đa

mStart.Uint64() memory bắt đầu được ép về kiểu Uint64 điều đó chúng tỏ biến này không vượt quá được giá trị 2^64 - 1, nếu vượt quá nó sẽ revert.

Con trỏ đến vị trí memory free

Có 5 byte 6080604052 đều xuất hiện trong mỗi smartcontract, chúng để làm gì vậy nhỉ?

Vì đẩy vào stack 80, đẩy vào stack 40, mstore sử dụng 2 tham số lần lượt lấy ra từ stack (40, 80) với (địa chỉ để ghi, giá trị cần ghi) và toàn bộ ý nghĩa của nó là: “T quy định chúng mày muốn thêm cái gì vào memory thì ghi từ địa chỉ 80, 00 → 70 bận rồi vì chúng nó có việc riêng cho hệ thống”

Đọc tiếp
Cách tạo ra một database lưu trữ dữ liệu từ đầu
Khổng Anh Dũng - Thứ5 15
Cách tạo ra một database lưu trữ dữ liệu từ đầu
Khổng Anh Dũng - Thứ5 15
Cách tạo ra một database lưu trữ dữ liệu từ đầu
Khổng Anh Dũng - Thứ5 15