Skip to content

Account Abstraction với ERC-4337 (Part 3): Wallet Creation

Posted on:April 6, 2023

Intro

Có một điều mà chúng ta chưa đề cập đến là wallet được tạo ra như thế nào? Cách “truyền thống” là sử dụng một EOA để deploy wallet contract lên trên mạng blockchain. Nó thực sự là một phương án tồi, vì chúng ta đã đi cả một chặng đường dài ở các phần trước để xây dựng một hệ thống có thể hoạt động mà không cần chuẩn bị thêm một EOA riêng biệt để kích hoạt giao dịch nữa. Nếu ta vẫn cần EOA ở thời điểm ban đầu khi tạo wallet, thì rõ ràng những điều ta xây dựng sau đó là vô nghĩa.

Ta sẽ làm rõ lại những gì ta muốn làm: một user chưa có wallet sẽ muốn có một wallet mới, on-chain, có thể tự trả phí gas bằng ETH hoặc tìm một paymaster để trả hộ, toàn bộ quá trình này không cần phải tạo thêm một EOA nào.

Khi ta tạo một EOA, ta có thể generate private key tại local và sở hữu account mà không cần send một transaction nào cả. Ta có thể share địa chỉ của ta cho người khác để nhận ETH hay token trước khi ta thực hiện bất cứ một transaction nào.

Ta cũng muốn việc tạo smart contract wallet của chúng ta trở nên tương tự như vậy, có nghĩa là ta cũng sẽ khả năng tạo ra và sở hữu wallet ngay dưới local, ta cũng có thể share địa chỉ này cho người khác để nhận token trước khi ta thực sự thực hiện một transaction.

Đấy là lúc ta cần đến CREATE2.

Tạo địa chỉ tất định (deterministic address) với CREATE2

Một địa chỉ contract đã được tính toán trước nhưng chưa deploy lên được gọi là một counterfactual address.

Với CREATE2, dù ta chưa thực sự deploy contract, nhưng từ các dữ liệu đầu vào ta đã tính toán được địa chỉ của nó, gọi là địa chỉ tất định (deterministic address). Và lúc này ta hoàn toàn đã có thể sử dụng địa chỉ này để nhận token được.

Đầu vào của CREATE2 bao gồm:

Có thể bạn không để ý: khi deploy một contract, code thực sự được lưu trên blockchain sẽ không giống với code mà ta đã submit lên.

Hơn thế nữa, sử dụng cùng một init code nhiều lần cũng không đảm bảo rằng contract được deploy lên sẽ luôn có code giống nhau, vì init code có thể đọc từ storage các biến số thay đổi theo thời gian, ví dụ TIMESTAMP chẳng hạn.

Thử nghiệm đầu tiên: Entry Point deploy contract bất kì

Giờ đây ta biết cách sử dụng của CREATE2, ta sẽ thử nghiệm cho phép user thêm init code vào operation và nhờ Entry Point tiến hành deploy contract nếu nó chưa tồn tại.

Đầu tiên ta sẽ add một trường mới vào bên trong User Operation:

struct UserOperation {
  // ...
  bytes initCode;
}

Sau đó, chúng ta sẽ update phần validation của Entry Point trong handleOps như sau: Với mỗi khi validate một op, nếu op có dữ liệu cho trường initCode, thì sử dụng CREATE2 để deploy một contract với initCode đó. Phần còn lại của validation thì vẫn tiến hành như bình thường:

Đây có thể coi là một thử nghiệm khá ổn! Nó hoàn tất được tất cả các nhiệm vụ ta cần: user có thể deploy bất kì contract nào và có thể biết trước được địa chỉ của contract trước khi nó được deploy, quá trình deployment cũng có thể được sponsored bởi paymaster, hoặc user có thể tự trả tiền (bằng cách gửi ETH vào địa chỉ contract trước khi nó được deploy).

Nhưng có một rủi ro rất lớn ở đây: initCode là mã code bất kì, nên ta không thể chắc chắn được rằng nó có phải là mã độc hại hay không?

Ta cần một giải pháp để user có thể an toàn khi deploy wallet contract, và những thành phần khác trong hệ thống cũng có thể được đảm bảo khi tương tác với các bản depoy này.

Ta sẽ giới thiệu contract mới: Factory.

Thử nghiệm tốt hơn: Factory

Thay vì để Entry Point nhận initCode bất kì và thực hiện CREATE2, ta cho phép user có quyền lựa chọn một contract để gọi CREATE2 thay thế. Ta gọi những contract này là Factory, được sử dụng chuyên biệt để tạo ra những wallet contract khác nhau.

Ví dụ factory này tạo ra multisig wallet contract yêu cầu 2/2 keys để mở khóa giao dịch, factory kia thì tạo ra 3/5 keys multisig wallet…

Factory sẽ có một method được gọi khi tạo contract:

contract Factory {
  function deployContract(bytes data) returns (address);
}

Hàm deployContract trả về địa chỉ của contract mới tạo ra, do đó user có thể simulate hàm này để biết được địa chỉ sẽ được tạo ra trước khi deploy. Điều này cũng đạt được mục tiêu ban đầu ta đã đề ra.

Chúng ta cũng sẽ cần thêm những trường mới vào bên trong User Operation để dùng cho việc deploy wallet:

struct UserOperation {
  // ...
  address factory;
  bytes factoryData;
}

aa-07

Bằng cách này, ta đã giải quyết được các vấn đề ở bên trên:

Vấn đề cuối cùng giống hệt với vấn đề mà ta đã gặp phải với validatePaymasterOp trong paymaster, và ta cũng sẽ xử lý tương tự như vậy.

Bundler sẽ giới hạn factory chỉ có thể truy cập các associated storage của nó và contract mà nó đang deploy mà thôi, đồng thời cũng ban đi như opcodes thay đổi theo thời gian như TIMESTAMP, BLOCKHASH

Chúng ta cũng sẽ yêu cầu các factory phải join vào reputation system bằng cách stake một lượng ETH giống như paymaster.

Ta cũng có trường hợp ngoại lệ tương tự như với paymaster, factory sẽ không cần phải stake nếu deployment method chỉ truy cập associated storage của wallet nó đang deploy, mà không truy cập associated storage của chính factory.

Ta đã xong với wallet creation!

Tại thời điểm này, với kiến trúc này ta đã có thể thực hiện toàn bộ những features trong ERC-4337!

Phần 4 cuối cùng ta sẽ đi vào triển khai aggregating signatures, để cung cấp khả năng tối ưu hóa gas cho các giao dịch.

Tham khảo