abi.encode
Encode bất kỳ kiểu dữ liệu nào thành dạng bytecode (hay bytes) mà có thể hiểu bởi EVM (máy ảo của Ethereum).
abi.encode(...) returns (bytes memory)Ví dụ:
function encodeNumber() public pure returns (bytes memory) {
bytes memory number = abi.encode(1);
return number;
}Kết quả khi gọi hàm sẽ là: 0x0000000000000000000000000000000000000000000000000000000000000001.
Một ví dụ khác:
function encodeString() public pure returns (bytes memory) {
bytes memory someString = abi.encode("some string");
return someString;
}Kết quả khi gọi hàm: 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e67000000000000000000000000000000000000000000.
Info
Chúng ta thường sử dụng
abi.encodekhi cần gửi dữ liệu đến contract.
abi.encodePacked
Giống abi.encode nhưng cho phép xóa bỏ các padding không cần thiết.
abi.encodePacked(...) returns (bytes memory)Về mặt kỹ thuật, nó sẽ thực hiện “Non-standard Packed Mode”:
- Các kiểu dữ liệu nhỏ hơn 32 bytes sẽ được nối lại trực tiếp mà không có padding hay sign extension (là quá trình lấp đầy các bit trống bằng bit dấu khi chuyển một số có kiểu dữ liệu ít bit sang một kiểu dữ liệu cói nhiều bit hơn).
- Dynamic type được encoded in-place (không yêu cầu cấp thêm bộ nhớ) mà không có kích thước.
- Các phần tử mảng được padded nhưng vẫn được encoded in-place.
Ví dụ, encode int16(-1), bytes1(0x42), uint16(0x03), string("Hello, world!") sẽ có giá trị là:
0xffff42000348656c6c6f2c20776f726c6421
^^^^ int16(-1)
^^ bytes1(0x42)
^^^^ uint16(0x03)
^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length fieldDo không có padding, việc sử dụng abi.encodePacked sẽ giúp tiết kiệm gas hơn là abi.encode nhưng nó không thích hợp cho việc gọi hàm của contract khác.
Seealso
Ví dụ thực tế:
function encodeString() public pure returns (bytes memory) {
bytes memory someString = abi.encodePacked("some string");
return someString;
}Việc sử dụng abi.encodePacked như trên tương tự với việc ép kiểu dữ liệu đối số sang kiểu bytes:
function encodeStringBytes() public pure returns(bytes memory) {
bytes memory someString = bytes("some string");
return someString;
}Tuy nhiên, sự khác nhau của hai hàm này nằm ở cách mà chúng được xử lý ở phía sau: abi.encodePacked sẽ sao chép bộ nhớ còn bytes là ép kiểu con trỏ. Tất nhiên, việc sao chép bộ nhớ sẽ tiêu tốn nhiều gas hơn.
Seealso
abi.decode
Hàm này nhận vào encoded data và một tuple chứa danh sách các kiểu dữ liệu mà ta cần decode.
abi.decode(bytes memory encodedData, (...)) returns (...)Ví dụ:
function encodeString() public pure returns (bytes memory) {
bytes memory someString = abi.encode("some string");
return someString;
}
function decodeString() public pure returns(string memory) {
string memory someString = abi.decode(encodeString(), (string));
return someString;
}Multi-encode/Multi-decode
Ta có thể encode nhiều giá trị và decode nhiều giá trị như sau:
function multiEncode() public pure returns(bytes memory){
bytes memory someString = abi.encode("some string", "it's bigger!");
return someString;
}
function multiDecode() public pure returns(string memory, string memory){
(string memory someString, string memory someOtherString) = abi.decode(multiEncode(),(string,string));
return (someString, someOtherString)
}Mặc dù có thể sử dụng abi.encodePacked cho nhiều giá trị:
function multiEncodePacked() public pure returns (bytes memory){
bytes memory someString = abi.encodePacked("some string", "it's bigger!");
return someString;
}Nhưng ta không thể decode output của abi.encodePacked. Đoạn code dưới đây sẽ quăng lỗi:
function multiDecodePacked() public pure returns (string memory, string memory){
string memory someString = abi.decode(multiEncodePacked(), (string));
return someString;
}Thay vào đó, ta có thể sử dụng ép kiểu để decode:
function multiStringCastPacked() public pure returns (string memory){
string memory someString = string(multiEncodePacked());
return someString;
}abi.encodeWithSelector
ABI-encode function selector và các đối số của hàm bắt đầu từ đối số thứ 2.
abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)Ví dụ:
function transfer(address someAddress, uint256 amount) public {
s_someAddress = someAddress;
s_amount = amount;
}
function getSelectorOne() public pure returns (bytes4 selector) {
selector = bytes4(keccak256(bytes("transfer(address,uint256)")));
}
function getDataToCallTransfer(address someAddress, uint256 amount)
public
pure
returns (bytes memory)
{
return abi.encodeWithSelector(getSelectorOne(), someAddress, amount);
}Chúng ta có thể sử dụng giá trị trả về của abi.encodeWithSelector làm calldata để gọi đến hàm của contract:
function callTransferWithBinary(address someAddress, uint256 amount)
public
returns (bytes4, bool)
{
(bool success, bytes memory returnData) = address(this).call(
abi.encodeWithSelector(getSelectorOne(), someAddress, amount)
);
return (bytes4(returnData), success);
}Có thể thấy, giá trị trả về của call sẽ là một biến boolean cho biết tx có thành công hay không và một biến kiểu bytes lưu giữ giá trị trả về của hàm được gọi bởi call. Trong đoạn code trên, returnData sẽ rỗng do hàm được gọi (transfer) không trả về dữ liệu gì.
abi.encodeWithSignature
Hàm này giống với hàm [[#abiencodewithselector|abi.encodeWithSelector]] nhưng thay vì truyền vào function selector thì ta truyền vào function signature ở dạng chuỗi.
Ví dụ:
function callTransferWithBinarySignature(
address someAddress,
uint256 amount
) public returns (bytes4, bool) {
(bool success, bytes memory returnData) = address(this).call(
abi.encodeWithSignature(
"transfer(address,uint256)",
someAddress,
amount
)
);
return (bytes4(returnData), success);
}