LapTrinhBlockchain

Chia sẻ kiến thức về Lập Trình Blockchain

Lập trình Blockchain, Lập trình Smart Contract

Hướng dẫn lập trình và triển khai Smart Contract trên TRON network

Hướng dẫn lập trình và triển khai Smart Contract trên TRON network

Hướng dẫn lập trình và triển khai Smart Contract trên TRON network

Chia sẻ bài viết
5
(6)

Mục lục

Tìm hiểu một chút về hệ sinh thái TRON

TRON Network là một blockchain công khai, một mạng lưới phi tập trung được xây dựng bởi TRON Foundation. TRON Foundation được thành lập bởi giám đốc điều hành Justin Sun. TRON dựa trên cơ chế Proof-of-Stake được ủy quyền (DPoS), trong đó 27 Siêu Đại Diện (Super Representative) luân phiên nhau 6 giờ một lần để xác thực các block và giao dịch. Hợp đồng thông minh có thể được viết bằng Solidity và được biên dịch trên Máy Ảo TRON (TVM).

Mainet của TRON được phát hành tháng 06/2018, mới đầu TRON được cộng đồng Fomo khá mạnh, hệ sinh thái thời kỳ đầu ra mắt phát triển khá nhưng sau này bắt đầu lắng dần. Cho đến hiện tại hệ sinh thái gần như không thay đổi nhiều, hầu hết thì đều là các ứng dụng của TRON Foundation.

Các sản phẩm của TRON Foundation thực ra không có gì sáng tạo, hầu hết đều được clone từ các sản phẩm nổi tiếng trên Ethereum và chỉnh sửa lại 1 chút:

  • TVM trên TRON gần giống EVM của Ethereum
  • TRON IDE, một công cụ triển khai Smart Contract giống với Remix
  • Sunswap V1 trên TRON giống với Uniswap V1
  • Sunswap V2 trên TRON giống với Uniswap V2
  • PSM trên TRON giống với PSM của MakerDao
  • 2Pool trên TRON giống pool trên Curve Finance

Trước đây phí trên TRON khá rẻ nhưng đến hiện tại 10/2022 thì phí swap tầm 1.4$, phí này khá cao, cao hơn khá nhiều so với trên BSC. Để tiết kiệm phí các Bot giao dịch thường sử dụng Energy. Để có Energy bạn phải stake TRX hoăc mua trên Token Goodies.

Chuẩn bị trước khi lập trình Smart Contract trên TRON

Cài đặt ví TronLink

Đầu tiên bạn cần phải cài đặt ví TronLink, bạn nên cài đặt extension trên Web để tiện cho việc lập trình và triển khai Smart Contract.

Sau khi cài đặt xong hãy tạo 1 ví mới để bắt đầu, sau đó bạn chuyển sang mạng Testnet (Shasta hoặc Nile) => Mình hay sử dụng Nile để test. Tiếp theo vào link sau để claim TRX và một số token phục vụ cho việc Test: https://nileex.io/join/getJoinPage

Tìm hiểu và sử dụng TRON IDE

Với một người mới, và với Smart Contract không quá phức tạp thì sử dụng TRON IDE là tiện và đơn giản nhất. Bạn xem qua giao diện, các sử dụng nó build, triển khai và gọi hàm. Cách dùng hoàn toàn giống với Remix.

Nếu chưa quen bạn có thể search Google thêm để tìm hiểu về công cụ này.

4 chuẩn token trên TRON blockchain

Hiện TRON có 3 chuẩn token như sau (Chi tiết xem: Token Standards Overview):

  • TRX: Là đơn vị tiền tệ chính thức trên mạng TRON. TRX có thể sử dụng để bỏ phiếu, hay stake để lấy Bandwidth và Energy.
  • Token TRC10: Một tiêu chuẩn mã thông báo kỹ thuật được hỗ trợ bởi chuỗi khối TRON nguyên bản không có Máy ảo TRON (TVM)
    • Ban đầu TRC10 khá phổ biến do nó rất dễ sử dụng, bạn không cần code cũng có thể phát hành được token TRC10 trên TRON.
    • Mọi tài khoản đều có thể phát hành token TRC10 với chi phí 1024 TRX
    • Nhưng TRC10 không thể tăng nguồn cung mà chỉ có thể đốt bớt token thôi.
    • Giao dịch TRC10 chỉ tiêu thụ Bandwidth
  • Token TRC20: Một tiêu chuẩn kỹ thuật được sử dụng cho các hợp đồng thông minh trên chuỗi khối TRON để triển khai mã thông báo với Máy ảo TRON (TVM). Nó hoàn toàn tương thích với ERC‌-20.
    • Gần đây TRC20 phổ biến hơn do hầu hết các DApp đều sử dụng TRC20. Ví dụ: BTT trước đây (BTT-OLD) sử dụng token TRC10, sau này đã chuyển sang token theo chuẩn TRC20 (BTT).
    • TRC20 tiêu thụ cả Bandwidth và Energy.
  • TRC721: Chuẩn cho các NFT trên mạng TRON, hoàn toàn tương thích với ERC-721 trên Ethereum.

Hiểu về khái niệm Energy và Bandwidth

Hiểu về Energy và Bandwidth

Đây là hai khái niệm khá mới trên TRON mà trên Ethereum không có. Chi tiết giải thích bạn xem bài viết: TRON bandwidth and energy explained. Mình xin nói lại một cách đơn giản dễ hiểu hơn.

Khi thực hiện một giao dịch trên TRON Network bạn cần phải trả phí cho nó, trên TRON sẽ thu phí này thông qua BandwidthEnergy:

  • Bandwidth: Tài nguyên giúp bạn thực hiện các giao dịch trên TRON, với mỗi giao dịch hệ thống sẽ tự động trừ Bandwidth của bạn.
    • Mỗi giao dịch sẽ tiêu tốn một lượng Bandwidth khác nhau, hệ thống sẽ tự động trừ lượng Bandwidth khác nhau. Trường hợp tài khoản không đủ Bandwidth, hệ thống sẽ tự động đốt TRX để bù lượng Bandwidth bị thiếu với tỉ giá quy đổi: 1 TRX = 1000 Bandwith
    • Mỗi ví đã được kích hoạt hàng ngày sẽ được cấp 1500 Bandwidth để sử dụng, bạn xem thông tin này trên Tron Explorer, ví dụ: TMzqLUTWMeqgGmPZJtZ4ReivxWngvPsFVS
    • Thao tác chuyển TRX và token TRC10 không phải tương tác với Smart Contract nên chỉ sử dụng Bandwidth,mỗi giao dịch transfer sẽ tiêu tốn dưới 500 Bandwidth, lượng Bandwidth thay đổi tùy vào ví nhận đã được kích hoạt hay chưa, nếu chưa được kích hoạt sẽ cần lượng nhiều hơn. Như vậy lượng bandwidth được cấp hàng ngày bạn có thể thực hiện được trên 3 giao dịch chuyển TRX hoặc token TRC10.
  • Energy: Đây là tài nguyên đặc biệt được sử dụng để để xử lý các hợp đồng thông trên mạng TRON.
    • Như vậy các giao dịch tới các Smart Contract sẽ phải sử dụng cả Bandwidth và Energy. Việc chuyển token TRC20 yêu cầu phải thao tác Smart Contract nên phải sử dụng cả hai tài nguyên Bandwidth và Energy.
    • Tùy thuộc từng giao dịch gọi smart contract mà lượng energy tiêu thụ sẽ khác nhau, nhưng nói chung khá tốn. Nếu hết energy hệ thống sẽ tự động đốt TRX để bù lượng energy bị thiếu với tỉ lệ quy đổi là: 1 TRX = 3571.42857 Energy. Đây ví dụ giao dịch đốt TRX để bù Energy: 1c44f204ce04a359a3aff7c28eadee47cd23d16381380519b941ac267d42f201

Energy bị giới hạn mỗi ngày nên lượng TRX stake để lấy energy càng nhiều thì lượng energy nhận được trên mỗi TRX càng ít, bạn vào trang TronStation của BTCSchools để tính. Bình thường mình không để ý vấn đề này cho đến một ngày tự nhiên thấy bot bình thường đang có 28M energy tự nhiên giảm về còn 16M energy, hỏi bên dev Tron sao tự nhiên energy giảm thì nó bảo do Justin sun lấy 2B TRX đem vào Stake2.0. Sau đó vài tiếng sau thì lượng energy bị giảm về còn 11M, cái này mình nghĩ do người dùng cho TRX vào Stake2.0 để có energy choi SunPump.

Việc này sẽ làm cho chi phí trên mỗi giao dịch trên TRON ngày càng đắt đỏ. Vì thế JustinSun cũng chuẩn bị ra đề xuất để tăng “Energy Limit” lên (Chi tiết xem Twitter):

Stake TRX để nhận Energy/Bandwidth

Như vậy mỗi giao dịch trên TRON, bạn cần phải có Bandwidth và Energy làm phí, nếu không có phải trả lượng TRX tương đương. Trước đây phí trên TRON khá rẻ, nhưng gần đây phí giao dịch trên TRON khá đắt nên bạn cần quan tâm tới nó.

Vậy có cách nào để có Bandwidth và Energy mà bạn không cần phải đốt TRX không? Câu trả lời là có, bạn phải stake TRX để đổi lấy Bandwidth hoặc Energy:

  • Mỗi lần stake bạn chỉ đổi lấy Energy hoặc Bandwith. Ví A có thể stake để lấy bandwitdth cho ví B. Với energy có được thông qua Stake, mỗi khi Energy được sử dụng, sau một thời gian Energy sẽ được hồi dần.
  • Sau khi Stake, ngoài nhận được Energy hoặc Bandwidth, bạn còn nhận được TRON Power, bạn sử dụng cái này để vote cho các node để nhận thêm phần thưởng TRX do node trả cho bạn. Link để vote: https://tronscan.org/#/sr/votes
  • Sau khi stake bạn cần phải chờ tối thiểu 3 ngày mới được unstake.
  • Để stake bạn vào link sau https://tronscan.org/#/wallet/resources và kết nối tới ví TronLink, sau đó kích nút Obtain. Tỉ giá quy đổi khi stake như sau (Tham khảo trên trang (ENERGY/BANDWIDTH CALCULATORS):
    • 1 TRX = 27.75472 Energy
    • 1 TRX = 1.06005 Bandwidth
  • Giả sử mỗi giao dịch swap tiêu thụ tầm 79000 Energy (Tham khảo giao dịch: e03bb5cf81a6e94be8cb3c30506755bd607db252b768f6829e2202aae343afa9), bot của bạn mà giao dịch tầm 100 giao dịch / ngày thì bạn cần phải stake ~284636 TRX (~17817$) => Một lượng tài sản không hề nhỏ.
Kích nút Obtain để stake TRX
Kích nút Obtain để stake TRX
Giao diện để stake TRX
Giao diện để stake TRX

Mua Energy/Bandwidth

Nếu bạn muốn giao dịch rẻ, nhưng bạn không có quá nhiều TRX để stake thì bạn phải làm sao? Có một giải pháp đó là mua Energy và Bandwidth trên trang Token Goodies. Thực chất là bạn bỏ tiền thuê một người đang giữ TRX stake để cung cấp energy/bandwidth cho mình.

Với cách này bạn tiết kiệm được 62% chi phí, giá cả thay đổi tùy từng thời điểm.

Làm thế nào để tính fee từ energy

Khi bạn lấy thông tin từ giao dịch qua lệnh “tronWeb.trx.getTransactionInfo(txId)” thì bạn sẽ thấy thông tin như sau:

{
  id: 'a56eaaa672d2064af1b8828a1c8bbc042d2fb646f30f72de41584d182fba5e41',
  fee: 25116220,
  blockNumber: 42837409,
  blockTimeStamp: 1703167437000,
  contractResult: [ '' ],
  contract_address: '4107f8614b0bb0979e0c7d95e43ddf5bd2b6535ace',
  receipt: {
    energy_fee: 22844220,
    energy_usage_total: 54391,
    net_fee: 2272000,
    result: 'SUCCESS'
  },
  ...
}

Thông tin energy/bandwidth ở trong trường receipt gồm các trường:

  • energy_usage: Lượng energy đã sử dụng cho giao dịch
  • energy_fee: Lượng TRX đã bị burn để tạo energy sử dụng cho giao dịch
  • energy_usage_total: Tổng energy dùng cho giao dịch (Gồm cả energy_usage và energy_fee)
  • net_usage: Lượng bandwidth sử dụng cho giao dịch
  • net_fee: Lượng TRX đã bị burn để tạo bandwidth để sử dụng cho giao dịch

Như vậy thông tin như sau:

  • Tổng energy của giao dịch sử dụng là energy_usage_total
  • Tổng TRX bị burn cho giao dịch: (energy_fee + net_fee)/10^6

Một số kiến thức cơ bản trước khi lập trình Smart Contract

Smart Contract trên TRON viết bằng Solidity, nên các bạn phải biết sơ sơ về ngôn ngữ này. Bạn có thể tham khảo tại: Solidity TutorialSolidityLang. Và bạn xem OpenZeppelin, một thư viện smart contract được sử dụng nhiều nhất hiện nay.

Các thành phần của một Smart Contract

Gồm 2 thành phần chính là DataFunction. Ngoài ra còn có Event and Log.

Data

Mọi dữ liệu Smart Contract phải được gán cho một vị trí: Storage hoặc Memory. Sẽ rất tốn kém khi sửa đổi dữ liệu Storage, vì vậy bạn cần cân nhắc nơi dữ liệu của mình sẽ được lưu trữ một cách khôn ngoan.

* Storage: Dữ liệu lưu trữ lâu dài sẽ được lưu trong Storage và được biểu thị như các biến trạng thái. Các giá trị này được lưu trữ vĩnh viễn trên blockchain. Biến lưu trong Storage được khai báo như sau:

contract SimpleStorage {
    uint storedData; // State variable
    // ...
}

Các kiểu dữ liệu hỗ trợ gồm: address, boolean, integer, fixed point numbers, fixed-size byte arrays, dynamically-sized byte arrays, Rational and integer literals, String literals, Hexadecimal literals, Enums

Bạn chú ý rằng dữ liệu address trên Smart Contract theo định dạng Hexa, chứ không phải địa chỉ bình thường bạn hay sử dụng, địa chỉ TRON bình thường bạn hay nhìn thấy theo chuẩn Base58. Ví dụ tài khoản TA9h822trLafTtsGXQc4g4ehPvyNzkQNsS thì địa chỉ ở dạng Hexa là 4101fba20cb405734c6b2e704b9ed67c0b5ea74d9e => Khi đó trong Smart Contract bạn phải bỏ kí tự 41 ở đầu đi (41 tương ứng tiền tố T):

address newAddress = 0x01fbA20CB405734C6B2e704B9eD67C0b5ea74D9E

Trên NodeJs, bạn có thể sử dụng hàm tronWeb.address.toHex() để chuyển từ địa chỉ của TRON sang dạng Hexa:

# Chuyển từ địa chỉ TRON sang dạng Hexa
tronWeb.address.toHex("TNPeeaaFB7K9cmo4uQpcU32zGK8G1NYqeL")
> "418840E6C55B9ADA326D211D818C34A994AECED808"

# Chuyển từ địa chỉ dạng Hexa sang địa chỉ trên TRON
tronWeb.address.fromHex("418840E6C55B9ADA326D211D818C34A994AECED808")
> "TNPeeaaFB7K9cmo4uQpcU32zGK8G1NYqeL"

* Memory: Các giá trị chỉ được lưu trữ trong suốt thời gian thực thi của một hàm được gọi là các biến Memory. Vì chúng không được lưu trữ vĩnh viễn trên blockchain nên chúng rẻ hơn nhiều khi sử dụng.

* Biến môi trường: Ngoài ra có nhiều biến toàn cục bạn có thể sử dụng để lấy thông tin về blockchain hoặc giao dịch hiện tại. Một số biến toàn cục hay dùng:

  • block.timestamp: Kiểu uint256, thời gian khối hiện tại tính bằng giây.
  • block.number: Kiểu uint, số block hiện tại
  • block.coinbase: Kiểu address, địa chỉ của node siêu đại diện tạo ra khối hiện tại
  • msg.sender: Kiểu address, Người gửi (Người gọi Smart Contract hiện tại)
  • msg.value: Kiểu uint, số lượng TRX gửi kèm
  • msg.data: Kiểu bytes, chứa toàn bộ dữ liệu gửi
  • msg.sig: Kiểu bytes4, mã định danh hàm gọi.
  • now: Kiểu uint, chính là block.timestamp

Function

Có hai kiểu gọi hàm:

  • internal: Các hàm và biến storage nội bộ chỉ có thể truy cập nội bộ (Tức là từ bên trong hợp đồng hiện tại, hoặc hợp mở rộng từ nó). Các hàm này không thể được gọi từ EVM.
  • external: Các hàm này là một phần của giao diện hợp đồng, chúng được gọi từ các hợp đồng khác hoặc thông qua các giao dịch. Các hàm này cho phép gọi từ EVM. Khi gọi hàm external từ bên trong hợp đồng bạn phải sử dụng thêm với this, chẳng hạn: this.f()

Các hàm cũng có thể là public hoặc private:

  • public: Các hàm public có thể được gọi từ bên trong nội bộ hợp đồng hoặc từ bên ngoài thông qua message.
  • private: Chỉ gọi từ bên trong nội bộ hợp đồng

Dưới đây là ví dụ hàm public:

function update_name(string memory value) public {
    dapp_name = value;
}

Chúng ta cần phân biệt thêm 2 loại Function: View/Pure/Payable function, ConstructorBuilt-in function:

  • View function: Không cho phép thay đổi trang thái của dữ liệu hợp đồng. Hàm truy vấn balance là một ví dụ:
    function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerPizzaCount[_owner];
    }
  • Pure function:
  • Payable function:
  • Constructor function: Chỉ được thực thi 1 lần khi hợp đồng được triển khai lần đầu tiên. Hàm này thường dùng để khởi tạo các biến trạng thái.
    constructor() public {
    owner = msg.sender;
    }
  • Built-in function: Đây là các hàm được tích hợp sẵn, như hàm address.send() để gửi TRX sang tài khoản khác.

Nhưng thao tác sau sẽ làm thay đổi trang thái của Smart Contract:

  • Thay đổi giá trị các biến trạng thái
  • Phát sự kiện
  • Tạo một contract khác
  • Sử dụng selfdestruct
  • Gửi TRX
  • Gọi các hàm không được đánh dấu là view hoặc pure
  • Sending trx via calls.
  • Sử dụng các lời gọi cấp thấp (low-level call)
  • Sử dụng mã nhúng assembly

Các thành phần của một function gồm:

  • Từ khóa function
  • Tên function
  • Danh sách tham số và kiểu dữ liệu tương ứng
  • Kiểu truy cập: public/private/internal/external
  • Đánh dấu hàm nếu có: pure/view/payable
  • Kiểu dữ liệu trả về nếu có

Dưới đây là một contract mẫu:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.6.0;

contract ExampleDapp {
    string dapp_name; // state variable

    // Called when the contract is deployed and initializes the value
    constructor() public {
        dapp_name = "My Example dapp";
    }

    // Get Function
    function read_name() public view returns(string memory) {
        return dapp_name;
    }

    // Set Function
    function update_name(string memory value) public {
        dapp_name = value;
    }
}

Event and Log

Event cho phép chúng ta dễ dàng truy vấn “những điều” đã xảy ra khi một giao dịch hợp đồng được thực hiện.

Log được sử dụng để “ghi” dữ liệu vào cấu trúc dữ liệu bên ngoài hợp đồng thông minh. Thông tin Log không thể được truy cập bởi các hợp đồng thông minh, nhưng có thể cung cấp thông tin về các giao dịch và những gì đã xảy ra trong các khối.

Khi một giao dịch hợp đồng được thực hiện thành công, hợp đồng thông minh có thể phát ra các Event và ghi Log vào blockchain.

Fallback function

Fallback function là hàm đặc biệt trong Smart Contract của TRON cũng như các EVM. Nó là hàm không tên, không có đối số và không trả về bất cứ thứ gì. Hàm này được gọi nếu như không có hàm nào khác khớp với mã định danh chức năng đã cho. Trước đây trên TRON, hàm này không tên, nhưng trong phiên bản Solidity sau này, bạn phải dùng tên receive() hoặc fallback().

Với các hợp đồng thông minh trên TRON, hàm Fallback sẽ không được kích hoạt khi nhận TRX thông thường. Nó liên quan tới 1 đề xuất trên TRON: https://github.com/tronprotocol/TIPs/issues/11. Chi tiết xem: Fallback Functions

Trước đây, với đặc biệt hàm này được gọi khi bạn nhận được TRX (Do 1 địa chỉ khác gửi) vì thế trong nhiều Smart Contract nó được dùng để:

  • Cho phép / Không cho phép Smart Contract nhận TRX.
    • Cho phép nhận TRX: Nếu Smart Contract muốn nhận TRX thì nó phải được đánh dấu là payable.
    • Không nhận TRX: Bỏ đánh dấu payable khỏi hàm, hoặc trong hàm này gọi hàm revert()
  • Thực hiện các xử lý khi nhận được TRX: Một số smart contract sẽ thực hiện các xử lý tự động khi nhận được TRX.

Trong phiên bản Solidity mới, ta dùng hàm receive() để xử lý nhận TRX. Trong ví dụ dưới, chúng ta sẽ đếm số giao dịch nhận TRX nhưng hiện nay không còn ý nghĩa nữa:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0;

contract Test {
    uint x;
  
    receive() external payable {
        x = x + 1;
    }
  
    function get() public view returns (uint) {
        return x;
    }
}

Smart Contract Library

Bạn không cần phải viết mọi hợp đồng thông minh trong dự án của mình từ đầu. Có rất nhiều thư viện hợp đồng thông minh mã nguồn mở có sẵn cung cấp các khối xây dựng có thể tái sử dụng cho dự án của bạn có thể giúp bạn không phải phát minh lại bánh xe.

OpenZeppelin Contracts là thư viện rất nổi tiếng và được rất nhiều Dev trên toàn thế giới sử dụng. Các contract hay dùng như:

Lập trình Smart Contract trên TRON

Để đơn giản cho việc code và triển khai Smart Contract, chúng ta sẽ sử dụng TronIDE, còn bạn nào muốn tạo Smart Contract phức tạp thì có thể sử dụng TronBox.

Ở đây chúng ta học thông qua việc xây dựng các Smart Contract cụ thể với các nghiệp vụ hay được sử dụng trong thực tế.

Smart Contract để lấy thông tin tài sản, chuyển và nhận tài sản

Dưới đây là contract sẽ có các hàm để:

  • Lấy thông tin tài sản: Gồm có TRX, TRC10 và TRC20. Có 3 hàm tương ứng:
    getTrxBalance()
    getTrc10Balance()
    getTrc20Balance()
  • Hỗ trợ chuyển tiền tới contract: Với token TRC20 bạn có thể chuyển thoái mái tới contract nhưng với TRX và token TRC10, bạn muốn contract của mình nhận được tiền bạn phải khải báo hàm sau (Ko cần cài đặt gì cả):
    receive() external payable { }
  • Rút tiền ra khỏi contract: Để hỗ trợ rút tiền ra khỏi contract, bạn bắt buộc phải cài đặt thêm hàm để làm việc này. Ba hàm dưới được cài đặt để hỗ trợ rút TRX, TRC10 và TRC20 ra khỏi contract:
    withdraw()
    withdrawTokenTrc10()
    withdrawTokenTrc20()

Toàn bộ mã nguồn của Contract như sau:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.6;

interface ITRC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint value) external returns (bool);
}

contract TronBalance  {
    address public owner;
    
    modifier onlyOwner() {
        require(msg.sender == owner, 'Only the owner can perform this action');
        _;
    }

    constructor() {
        owner = msg.sender;
    }
    
    // Allow send TRX, TRC10 token to this contract
    receive() external payable {
    }

    // Get balance of TRX
    function getTrxBalance(address account) external view returns (uint256) {
        return account.balance;
    }
    
    // Get balance of TRC10 token
    function getTrc10Balance(address account, uint256 tokenId) external view returns (uint256) {
        return account.tokenBalance(trcToken(tokenId));
    }

    // Get balance of TRC20 token
    function getTrc20Balance(address account, address token) external view returns (uint256) {
        return ITRC20(token).balanceOf(account);
    }
    
    // Withdraw TRX to the an account
    function withdraw(address payable to, uint amount) public onlyOwner {
        to.transfer(amount);
    }

    // Witdraw token TRC10
    function withdrawTokenTrc10(address payable to, uint256 tokenId, uint256 amount) public onlyOwner {
        to.transferToken(amount, trcToken(tokenId));
    }
    
    // Witdraw token TRC20
    function withdrawTokenTrc20(address to, address tokenAddr, uint256 amount) public onlyOwner {
        ITRC20(tokenAddr).transfer(to, amount);
    }
}

Một số chú ý khi lập trình với Smart Contract trên TRON

Chuyển đổi địa chỉ của Tron sang Hexa và ngược lại

Trong một số trường hợp bạn cần chuyển đổi địa chỉ Tron thành địa chỉ dạng Hexa, trên NodeJs bạn có thể sử dụng thư viện tronweb.

const TronWeb = require('tronweb');

function addressToHexa(account) {
    let addr = TronWeb.address.toHex(account);

    // Remove '41' at first
    if (addr.startsWith("41")) addr = addr.slice(2);
    return addr;
}

function hexaToAddress(hexaAddr) {
    // Remove 0x mark
    if (hexaAddr.startsWith("0x")) hexaAddr = hexaAddr.slice(2);
    // Add '41' at first
    if (!hexaAddr.startsWith('41')) hexaAddr = "41" + hexaAddr;
    return TronWeb.address.fromHex(hexaAddr);
}

function main() {
    let addresses = [
        'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb',
        'TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR',
        'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'
    ];
    for (let idx=0; idx<addresses.length; idx++) {
        let addr = addresses[idx];
        let hexa = addressToHexa(addr);
        console.log(`${addr} => ${hexa} => ${hexaToAddress(hexa)}`);
    }
}
main();

Kết quả bạn thấy:

T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb => 0000000000000000000000000000000000000000 => T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb
TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR => 891cdb91d149f23b1a45d9c5ca78a88d0cb44c18 => TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t => a614f803b6fd780986a42c78ec9c7f77e6ded13c => TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t

Chú ý: Trong SmartExchangeRouter của SunSwapV3 thì coi địa chỉ T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb là địa chỉ của TRX (Tương ứng địa chỉ Address(0) trên Hexa)

So sánh xâu (String compare)

Trên smart contract không hỗ trợ so sánh hai xâu trực tiếp. Ví dụ:

// Không hỗ trợ
require(tokenSymbol=="USDT", "The token is not supported!");

Nếu bạn muốn so sánh xâu thì bạn cần cài đặt thêm hàm:

// Cài đặt hàm so sánh string
function _compareString(string memory a, string memory b) private pure returns (bool) {
    if (bytes(a).length != bytes(b).length) return false;
    return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
}

...
require(_compareString(tokenSymbol, "USDT"), "The token is not supported!");
...

Sự khác biệt giữa abi.encode, abi.encodePacked, abi.encodeWithSignature, abi.encodeWithSelector

Trong lập trình Smart Contract, để đóng gói (encode – Tôi dịch là đóng gói để phân biệt với encrypt là mã hóa) các dữ liệu thành mảng bytes ta thường sử dụng các hàm có sẵn trong abi bao gồm: abi.encode(), abi.encodePacked(), abi.encodeWithSignature(), abi.encodeWithSelector(). Giữa các hàm này sẽ có một số điểm khác nhau mà bạn cần chú ý để sử dụng cho các tình huống phù hợp:

  • abi.encode(): Đóng gói các tham số theo đặc tả ABI Specs. ABI được thiết kế để thực hiện các lệnh gọi đến Smart Contract. Mỗi một tham số đều được đóng gói theo kích thước cố định là 32 byte, cho dù kiểu dữ liệu của tham số đó có kích thước nhỏ hơn 23 byte. Nếu bạn đang thực hiện cuộc gọi đến một hợp đồng, bạn có thể phải sử dụng abi.encode().
  • abi.encodePacked(): Đóng gói tham số đảm bảo không gian lưu trữ là ít nhất. Việc đóng gói phụ thuộc vào kiểu của từng dữ liệu. Ví dụ kiểu unit8 thì sẽ sử dụng 1 byte, kiểu unit256 sẽ sử dụng 32 bytes. Sử dụng cách này bạn sẽ loại bỏ được nhiều dữ liệu dư thừa và tiết kiệm được nhiều không gian bộ nhớ nhất. Thường cách này để đóng gói truyền dữ liệu, chú không dùng để tạo lệnh gọi Smart Contract.
  • abi.encodeWithSignature(): Giống như hàm abi.encode() nhưng sẽ thêm phần chữ ký là tham số đầu tiên. Sử dụng khi chữ ký được biết và bạn không muốn tính toán selector.
  • abi.encodeWithSelector(): Giống như hàm abi.encode() nhưng selector là tham số đầu tiên. Nó gần bằng với encodeWithSignature – sử dụng bất cứ thứ gì phù hợp nhất.

Trong trao đổi dữ liệu, chúng ta thường sử dụng hàm abi.encode()abi.encodePacked() để đóng gói dữ liệu và khôi phục lại dữ liệu gốc chúng ta phải sử dụng các hàm tương ứng abi.decode()abi.decodePacked().

Trong gọi hàm Smart Contract, chúng ta sẽ có 4 cách gọi hàm khác nhau tương ứng với các hàm đóng gói ở trên:

// Cách 1: Đơn giản nhất, sử dụng interface
contract_instance.myfunction(400,500);

// Cách 2: Dùng abi.encodeWithSignature()
(success, ) = address(c).call(abi.encodeWithSignature("myfunction(uint256,uint256)", 400, 500));

// Cách 3: Dùng abi.encodeWithSelector()
(success, ) = address(c).call(abi.encodeWithSelector(bytes4(keccak256("myfunction(uint256,uint256)")), 400,500));

// Cách 4: 
(success, ) = address(c).call(bytes4(keccak256("myfunction(uint256,uint256)")), 400, 500);

Vấn đề giữa các phiên bản của TronWeb

Nhiều lỗi phát sinh do sử dụng phiên bản TronWeb cũ, vì thế bạn cần phải chú ý vấn đề này.

Tôi có phát triển ứng dụng trên Tron đầu năm 2021, lúc đó tôi sử dụng thư viện tronweb@3.2.7 (Mới nhất lúc đó). Sau đó dự án hoạt động ổn định nên thư viện này không được cập nhật lên nữa.

Đến tháng 06/2023, SunSwap ra phiên bản SunSwapV3 giống với UniswapV3, tôi lúc này cần hỗ trợ thêm SunSwapV3. Để tương tác với SunSwapV3 cần tương tác với 1 trong 2 contract sau:

  • SwapRouter: TQAvWQpT9H916GckwWDJNhYZvQMkuRL7PN => Thằng này giống với SwapRouter của Uniswap, nhưng thấy hơi kinh khi cấp quyền cho nó vì không có bot nào tương tác với Smart Contract này, contract này gần như không có giao dịch nào. Mặt khác nó chỉ hỗ trợ chuyển từ token TRC20 sang WTRX chứ không phải TRX.
  • SmartExchangeRouter: TFVisXFaijZfeyeSjCEVkHfex7HGdTxzF9 => Có rất nhiều giao dịch, được SunSwap UI sử dụng, số lượng giao dịch rất nhiều. Contract này hỗ trợ swap trên cả V1, V2, V3 và chuyển đổi từ WTRX sang TRX => Vì thế tôi sử dụng thằng này.

Trong SmartExchangeRouter có hàm quan trọng là hàm swapExactInput(). Hàm này mô tả như sau:

  struct SwapData {
    uint256 amountIn;
    uint256 amountOutMin;
    address to;
    uint256 deadline;
  }

  /**
   * @dev Exchange function for converting TRX to Token in a specified path.
   * @param path A specified exchange path from TRX to token.
   * @param poolVersion List of pool where tokens in path belongs to.
   * @param versionLen List of token num in each pool. versionLen means that the pathlength in the same version
   * @param data encodepacked swap info.
   * @return amountsOut Amount of Tokens bought corresponed to path.
   */
  function swapExactInput(
    address[] calldata path,
    string[] calldata poolVersion,
    uint256[] calldata versionLen,
    uint24[] calldata fees,
    SwapData calldata data
  ) external nonReentrant payable returns(uint256[] memory amountsOut)

Trong đó:

  • path là mảng địa chỉ các token theo thứ tự sẽ chuyển đổi. Nếu cần chuyển đổi giữa WTRX và TRX thì sử dụng v2 với địa chỉ TRX là T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb (Địa chỉ NULL).
  • poolVersion:
  • versionLen: Độ dài đường dẫn trong cùng một phiên bản. Tổng giá trị trong mảng này phải bằng kích thước mảng path. Ví dụ:
    path = [btc,eth,usdt,usdc]
    version = [‘v3′,’v2’]
    versionlen = [3,1]

    Điều này có nghĩa là swap như sau: btc-v3-eth-v3-usdt-v2-usdc

Chi tiết ABI của Contract này như dưới để các bạn tham khảo:

[
   {
      "inputs": [
         {
            "internalType": "address",
            "name": "_old3pool",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_usdcPool",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_v2Router",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_v1Foctroy",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_usdt",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_usdj",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_tusd",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_usdc",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_psmUsdd",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "_v3Router",
            "type": "address"
         }
      ],
      "stateMutability": "nonpayable",
      "type": "constructor"
   },
   {
      "anonymous": false,
      "inputs": [
         {
            "indexed": true,
            "internalType": "address",
            "name": "owner",
            "type": "address"
         },
         {
            "indexed": true,
            "internalType": "address",
            "name": "pool",
            "type": "address"
         },
         {
            "indexed": false,
            "internalType": "address[]",
            "name": "tokens",
            "type": "address[]"
         }
      ],
      "name": "AddPool",
      "type": "event",
      "stateMutability": "nonpayable"
   },
   {
      "anonymous": false,
      "inputs": [
         {
            "indexed": true,
            "internalType": "address",
            "name": "admin",
            "type": "address"
         },
         {
            "indexed": true,
            "internalType": "address",
            "name": "pool",
            "type": "address"
         },
         {
            "indexed": false,
            "internalType": "address[]",
            "name": "tokens",
            "type": "address[]"
         }
      ],
      "name": "ChangePool",
      "type": "event",
      "stateMutability": "nonpayable"
   },
   {
      "anonymous": false,
      "inputs": [
         {
            "indexed": true,
            "internalType": "address",
            "name": "buyer",
            "type": "address"
         },
         {
            "indexed": true,
            "internalType": "uint256",
            "name": "amountIn",
            "type": "uint256"
         },
         {
            "indexed": false,
            "internalType": "uint256[]",
            "name": "amountsOut",
            "type": "uint256[]"
         }
      ],
      "name": "SwapExactETHForTokens",
      "type": "event",
      "stateMutability": "nonpayable"
   },
   {
      "anonymous": false,
      "inputs": [
         {
            "indexed": true,
            "internalType": "address",
            "name": "buyer",
            "type": "address"
         },
         {
            "indexed": true,
            "internalType": "uint256",
            "name": "amountIn",
            "type": "uint256"
         },
         {
            "indexed": false,
            "internalType": "uint256[]",
            "name": "amountsOut",
            "type": "uint256[]"
         }
      ],
      "name": "SwapExactTokensForTokens",
      "type": "event",
      "stateMutability": "nonpayable"
   },
   {
      "anonymous": false,
      "inputs": [
         {
            "indexed": true,
            "internalType": "address",
            "name": "originOwner",
            "type": "address"
         },
         {
            "indexed": true,
            "internalType": "address",
            "name": "newOwner",
            "type": "address"
         }
      ],
      "name": "TransferAdminship",
      "type": "event",
      "stateMutability": "nonpayable"
   },
   {
      "anonymous": false,
      "inputs": [
         {
            "indexed": true,
            "internalType": "address",
            "name": "originOwner",
            "type": "address"
         },
         {
            "indexed": true,
            "internalType": "address",
            "name": "newOwner",
            "type": "address"
         }
      ],
      "name": "TransferOwnership",
      "type": "event",
      "stateMutability": "nonpayable"
   },
   {
      "stateMutability": "payable",
      "type": "fallback",
      "name": "fallback"
   },
   {
      "inputs": [],
      "name": "admin",
      "outputs": [
         {
            "internalType": "address",
            "name": "",
            "type": "address"
         }
      ],
      "stateMutability": "view",
      "type": "function"
   },
   {
      "inputs": [],
      "name": "owner",
      "outputs": [
         {
            "internalType": "address",
            "name": "",
            "type": "address"
         }
      ],
      "stateMutability": "view",
      "type": "function"
   },
   {
      "inputs": [],
      "name": "psmUsdd",
      "outputs": [
         {
            "internalType": "address",
            "name": "",
            "type": "address"
         }
      ],
      "stateMutability": "view",
      "type": "function"
   },
   {
      "inputs": [],
      "name": "v1Factory",
      "outputs": [
         {
            "internalType": "address",
            "name": "",
            "type": "address"
         }
      ],
      "stateMutability": "view",
      "type": "function"
   },
   {
      "inputs": [],
      "name": "v2Router",
      "outputs": [
         {
            "internalType": "address",
            "name": "",
            "type": "address"
         }
      ],
      "stateMutability": "view",
      "type": "function"
   },
   {
      "inputs": [],
      "name": "v3Router",
      "outputs": [
         {
            "internalType": "address",
            "name": "",
            "type": "address"
         }
      ],
      "stateMutability": "view",
      "type": "function"
   },
   {
      "stateMutability": "payable",
      "type": "receive",
      "name": "receive"
   },
   {
      "inputs": [
         {
            "internalType": "address",
            "name": "newOwner",
            "type": "address"
         }
      ],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "address",
            "name": "newAdmin",
            "type": "address"
         }
      ],
      "name": "transferAdminship",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "address",
            "name": "token",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "to",
            "type": "address"
         },
         {
            "internalType": "uint256",
            "name": "amount",
            "type": "uint256"
         }
      ],
      "name": "retrieve",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "string",
            "name": "poolVersion",
            "type": "string"
         },
         {
            "internalType": "address",
            "name": "pool",
            "type": "address"
         },
         {
            "internalType": "address[]",
            "name": "tokens",
            "type": "address[]"
         }
      ],
      "name": "addPool",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "string",
            "name": "poolVersion",
            "type": "string"
         },
         {
            "internalType": "address",
            "name": "pool",
            "type": "address"
         },
         {
            "internalType": "address[]",
            "name": "tokens",
            "type": "address[]"
         }
      ],
      "name": "addUsdcPool",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "string",
            "name": "poolVersion",
            "type": "string"
         },
         {
            "internalType": "address",
            "name": "pool",
            "type": "address"
         },
         {
            "internalType": "address",
            "name": "gemJoin",
            "type": "address"
         },
         {
            "internalType": "address[]",
            "name": "tokens",
            "type": "address[]"
         }
      ],
      "name": "addPsmPool",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "string",
            "name": "poolVersion",
            "type": "string"
         }
      ],
      "name": "isUsdcPool",
      "outputs": [
         {
            "internalType": "bool",
            "name": "",
            "type": "bool"
         }
      ],
      "stateMutability": "view",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "string",
            "name": "poolVersion",
            "type": "string"
         }
      ],
      "name": "isPsmPool",
      "outputs": [
         {
            "internalType": "bool",
            "name": "",
            "type": "bool"
         }
      ],
      "stateMutability": "view",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "address",
            "name": "pool",
            "type": "address"
         },
         {
            "internalType": "address[]",
            "name": "tokens",
            "type": "address[]"
         }
      ],
      "name": "changePool",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "address[]",
            "name": "path",
            "type": "address[]"
         },
         {
            "internalType": "string[]",
            "name": "poolVersion",
            "type": "string[]"
         },
         {
            "internalType": "uint256[]",
            "name": "versionLen",
            "type": "uint256[]"
         },
         {
            "internalType": "uint24[]",
            "name": "fees",
            "type": "uint24[]"
         },
         {
            "components": [
               {
                  "internalType": "uint256",
                  "name": "amountIn",
                  "type": "uint256"
               },
               {
                  "internalType": "uint256",
                  "name": "amountOutMin",
                  "type": "uint256"
               },
               {
                  "internalType": "address",
                  "name": "to",
                  "type": "address"
               },
               {
                  "internalType": "uint256",
                  "name": "deadline",
                  "type": "uint256"
               }
            ],
            "internalType": "struct SmartExchangeRouter.SwapData",
            "name": "data",
            "type": "tuple"
         }
      ],
      "name": "swapExactInput",
      "outputs": [
         {
            "internalType": "uint256[]",
            "name": "amountsOut",
            "type": "uint256[]"
         }
      ],
      "stateMutability": "payable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "uint256",
            "name": "amountMinimum",
            "type": "uint256"
         },
         {
            "internalType": "address",
            "name": "recipient",
            "type": "address"
         }
      ],
      "name": "unwrapWTRX",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
   },
   {
      "inputs": [
         {
            "internalType": "uint256",
            "name": "amountMax",
            "type": "uint256"
         }
      ],
      "name": "warpWTRX",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
   }
]

Sau đó tôi đã thử code với với hai cách gọi khác nhau:

// Thông tin pool
let paths: [
  'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb',    // TRX
  'TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR',    // WTRX
  'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'     // USDT
];
let fees: [ '0', '500', '0' ];
let poolVersions = [ 'v2', 'v3' ];
let versionLen = [ 2, 1 ];
let swapData = {
  amountIn: '1000000',
  amountOutMin: '77240',
  to: 'TMyH1HtwVM1wmksbV4xDfRpPKQj9nHn5Qu',
  deadline: 1688632568
};
let tuple = [ '1000000', '77240', 'TMyH1HtwVM1wmksbV4xDfRpPKQj9nHn5Qu', 1688632568 ];

// Tạo contract
let contract = ....;  // Tạo contract nhớ dùng ABI trên

// C1: Sử dụng cấu trúc
let result = await swapContract.methods.swapExactInput(
    paths,
    poolVersions,
    versionLen,
    fees,
    swapData
).send({
    feeLimit: '150000000',
    callValue: swapData.amountIn
});

// C2: Sử dụng mảng
let result = await swapContract.methods.swapExactInput(
    paths,
    poolVersions,
    versionLen,
    fees,
    tuple
).send({
    feeLimit: '150000000',
    callValue: swapData.amountIn
});

Nếu sử dụng tronweb@3.2.7 thì:

  • C1: Gọi lệnh thành công nhưng bị mất TRX mà không thấy thực hiện gì cả. Chi tiết xem giao dịch: 32ca62ce588da7591d82d8b659066ce824d9774c976d9ede7b57685e732fb740
  • C2: Báo lỗi luôn và không gửi được giao dịch
    error Error: types/value length mismatch (argument=”tuple”, value=[“254300000″,”19796901″,”TSpu3eoDZDf2DdNH2qE1qBKBjuz6L6SbH4”,1688703360797], code=INVALID_ARGUMENT, version=abi/5.6.1)

Sau đó tôi nâng cấp lên phiên bản tronweb@5.2.0:

Một số lỗi khi lập trình Smart Contract và DApp trên TRON và cách xử lý

Lỗi: error occurred class org.tron.core.exception.ContractValidateException :
Validate TransferContract error, no OwnerAccount

Nhiều khi bạn tạo một transaction trên TRON hay gặp lỗi này, ví dụ tôi dùng lệnh tronWeb.transactionBuilder.sendTrx() thì báo lỗi như dưới:

tronWeb.transactionBuilder.sendTrx(to, amount, from, options);
---------------------------------------------
error occurred class org.tron.core.exception.ContractValidateException : 
Validate TransferContract error, no OwnerAccount.

Lỗi này xảy ra do một số trường hợp sau:

  • Do địa chỉ from là tài khoản chưa được kích hoạt => Để sửa lỗi này, bạn chỉ cần kích hoạt địa chỉ from này bằng cách gửi ít nhất 1 TRX tới địa chỉ này
  • Do bạn không truyền địa chỉ from, khi đó from sẽ là địa chỉ mặc định lấy từ tronWeb => Khi đó nếu account mặc định của tronWeb mà là địa chỉ chưa được kích hoạt thì sẽ bị lại lỗi này => Để sửa lỗi này khi khởi tạo TronWeb, bạn phải truyền vào Private Key của một tài khoản đã kích hoạt.

Lỗi: error occurred class org.tron.core.exception.ContractValidateException :
Validate TransferContract error, balance is not sufficient

Lỗi này nhìn cũng biết là do không đủ TRX, TRX này có thể là TRX sử dụng trong giao dịch hoặc TRX sử dụng để làm phí.

Contract validate error : account [xxxxxxxxxxxxxxxxxxxxx] does not exist

Khi bạn tạo một transaction nên blockchain TRON, bạn nhận được thông báo như sau:

Contract validate error : account [xxxxxxxxxxxxxxxxxxxxxxxxxx] does not exist

Lỗi này do địa chỉ xxxxxxxxxxxxxxxxxxxxxxxxxx chưa được kích hoạt, bạn cần phải gửi ít nhất 1 TRX để kích hoạt địa chỉ này.

Lỗi: “REVERT opcode executed”

Lỗi này thường xảy ra do bạn gọi 1 lệnh hoặc thực hiện một giao dịch mà có quá nhiều xử lý.

Trên TRON network, khi tôi sử dụng Multicall3 để lấy thông tin của 30 Pool thì gặp lỗi này. Nếu trên Base thì tôi đã gọi cho 300 Pool vẫn chạy okie. Điều này chứng tỏ trên TRON network, phần check execution khá chặt. Để sửa lỗi này, bạn phải chia nhỏ các pool thành nhiều batch, mỗi batch từ 5 đến 10 pool.

Error: types/value length mismatch

Khi gọi hàm trên Contract từ TronWeb (Mạng Nile Testnet) thấy báo lỗi sau:

Error: types/value length mismatch (argument="tuple", value=["TXFouUxm4Qs3c1VxfQtCo4xMxbpwE3aWDM",false,"0xaa65a6c00000000000000000000000000000000000000000000000000000000000000001"], code=INVALID_ARGUMENT, version=abi/5.5.0)
    at Logger.makeError (/home/arbitest/arbi-collector/tool/node_modules/@ethersproject/logger/lib/index.js:199:21)
    at Logger.throwError (/home/arbitest/arbi-collector/tool/node_modules/@ethersproject/logger/lib/index.js:208:20)
    at Logger.throwArgumentError (/home/arbitest/arbi-collector/tool/node_modules/@ethersproject/logger/lib/index.js:211:21)
    at pack (/home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/coders/array.js:55:16)
    at TupleCoder.encode (/home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/coders/tuple.js:71:33)
    at /home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/coders/array.js:74:19
    at Array.forEach (<anonymous>)
    at pack (/home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/coders/array.js:60:12)
    at ArrayCoder.encode (/home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/coders/array.js:209:16)
    at /home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/coders/array.js:66:19
    at Array.forEach (<anonymous>)
    at pack (/home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/coders/array.js:60:12)
    at TupleCoder.encode (/home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/coders/tuple.js:71:33)
    at AbiCoder.encode (/home/arbitest/arbi-collector/tool/node_modules/ethers/node_modules/@ethersproject/abi/lib/abi-coder.js:93:15)
    at e.value (/home/arbitest/arbi-collector/tool/node_modules/tronweb/dist/TronWeb.node.js:1:37259)
    at e.value (/home/arbitest/arbi-collector/tool/node_modules/tronweb/dist/TronWeb.node.js:1:35325)
    at e.<anonymous> (/home/arbitest/arbi-collector/tool/node_modules/tronweb/dist/TronWeb.node.js:1:102182)
    at tryCatch (/home/arbitest/arbi-collector/tool/node_modules/@babel/runtime/helpers/regeneratorRuntime.js:44:17)
    at Generator.<anonymous> (/home/arbitest/arbi-collector/tool/node_modules/@babel/runtime/helpers/regeneratorRuntime.js:125:22)
    at Generator.next (/home/arbitest/arbi-collector/tool/node_modules/@babel/runtime/helpers/regeneratorRuntime.js:69:21)
    at asyncGeneratorStep (/home/arbitest/arbi-collector/tool/node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:24)
    at _next (/home/arbitest/arbi-collector/tool/node_modules/@babel/runtime/helpers/asyncToGenerator.js:22:9) {
  reason: 'types/value length mismatch',
  code: 'INVALID_ARGUMENT',
  argument: 'tuple'

Lỗi này liên quan tới thư viện TronWeb, do tôi sử dụng tronweb@3.2.7, một phiên bản khá cũ của TronWeb, chưa hỗ trợ kiểu tuple. Để sửa lỗi này chỉ cần nâng cấp thư viện TronWeb mới là okie, tôi đã nâng cấp lên tronweb@5.3.1 thì thấy chạy ngon lành.

TronBox: Invalid input source specified. Compilation failed. See above.

Khi build Smart Contract bằng TronBox, thấy có thông báo lỗi sau:

Compiling ./contracts/libs/FullMath.sol...
Compiling ./contracts/libs/LowGasSafeMath.sol...
Compiling ./contracts/libs/SafeCast.sol...
Compiling ./contracts/libs/SqrtPriceMath.sol...
Compiling ./contracts/libs/TransferHelper.sol...
Compiling ./contracts/libs/UnsafeMath.sol...
Compiling IERC20.sol...

Invalid input source specified.
Compilation failed. See above.

Lỗi này xảy ra do một câu lệnh import nào đó bị sai, thường liên quan tới đường dẫn. Điển hình là trường hợp dưới:

# Lệnh này khi build sẽ bị lỗi
import 'MyContract.sol';

# Lệnh này đúng
# Phải dùng kí tự . và ..
import './MyContract.sol';

Tài liệu tham khảo:

Bài viết này có hữu ích với bạn?

Kích vào một biểu tượng ngôi sao để đánh giá bài viết!

Xếp hạng trung bình 5 / 5. Số phiếu: 6

Bài viết chưa có đánh giá! Hãy là người đầu tiên đánh giá bài viết này.

Trả lời

Giao diện bởi Anders Norén