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.encode khi 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 field

Do 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);
}

Resources