12. NFTs | Encoding

NFTs

  • Stands for Non Fungible Token

    • 1 token is unique from all others

  • Based on ERC-721 standard

  • Contains a TokenURI

    • Returns a JSON object that contains metadata of NFT

    • JSON contains an image part which contains url of the image

      • This URL can be hosted on chain or IPFS or whereever


Pinata

  • It is a paid service which helps us pin our NFT data

  • It is centralized (not recommended)


NFT.Storage

  • Free storage to Pin our NFT Data

  • Based on FileCoin Blockchain

  • It is decentralized hence


Encoding & Opcodes

  • When we send a transaction, it is compiled down to bytecode and sent in data object of transaction

    • When a contract is created, its block's to address is null

  • EVM reads the bytecode using a reader

    • Each alphabet in the bytecode corresponds to an optcode instruction like 00 means HALT

abi.encode && abi.encodePacked

  • It is a globally available method of solidity

Use Case : String Concatenation

  • It returns a byte object

  • In versions ^0.8.12, we can do string.concat(stringA, stringB)

contract Encoding {
	function combineStrings() public pure returns (string memory) {
		return string(abi.encodePacked("Hello", "World"));
	}
}

Use Case : Others

  • Can encode anything to its binary

function encodeNumber() public pure returns(bytes memory) {
	bytes memory number = abi.encode(1);
	return number;
}

function encodeString() public pure returns(bytes memory) {
	bytes memory str = abi.encode("Hello");
	return str;
	// 00000000000000000000000x71231821289730129300000000000000000000000
}
  • Normal encode method takes a lot of memory i.e initial zeros, hence, we use encodePacked which sorts of works like a compressor

  • TypeCasting works similar but somewhat different behind the scenes

function encodeStringPacked() public pure returns(bytes memory) {
	bytes memory str = abi.encodePacked("Hello");
	return str;
	// 0x712318212897301293
}

abi.decode

  • We can also decode stuff

function decodeString() public pure returns (string memory) {
	string memory str = abi.decode(encodeString(), (string));
	return str;
}
  • We can also do multi decoding

function multiEncode() public pure returns (bytes memory) {
	bytes memory str = abi.encode("Hello", "World");
	return str;
}
function multiDecode() public pure returns (string memory, string memory) {
	(string memory str1, string memory str2) = abi.decode(multiEncode(), (string, string));
	return (str1, str2);
}
  • In order to decode packed objects, we must cast

function multiEncodePacked() public pure returns(bytes memory) {
	bytes memory str = abi.encodePacked("Hello", "World");
	return str;
}

function multiStringCastPacked() public pure returns (string memory) {
	bytes memory str = string(multiEncodePacked());
	return str;
}

Encoding Function Calls

call

  • We use call functions to change the state of the blockchain

  • In our {}, we are able to pass specific fields of transactions like value

  • In our (), we can pass data to call a specific function

Withdraw Use Case

We used call with data field empty to send or receive ether

function withdraw(address winner) public {
	(bool success, bytes memory response) = winner.call{value: address(this).balance}("");
	require(success, string(response));
}

Calling Other Functions Use Case

We need to encode the function name and parameters down to binary level

  • Each function has its function id known as function selector

    • It is first 4 bytes of the function signature

    • Function signature is a string that defines the function name and params

contract CallAnything {
	address public s_someAddress;
	uint256 public s_amount;

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

	function callTransferFunctionDirectly(address someAddress, uint256 amount) public returns(bytes4, bool) {
		(bool success, bytes memory returnData) = address(this).call(
			getDataToCallTransfer(someAddress, amount)
		);
		return(bytes4(returnData), success);
	}
	function callTransferFunctionDirectlySig(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);
	}
}

staticcall

  • We use staticcall to call our view or pure functions


Last updated