Skip to content

Instantly share code, notes, and snippets.

@thurendous
Last active July 10, 2023 14:47
Show Gist options
  • Save thurendous/a8bed9fe41abb6b8deff1af73acde8e9 to your computer and use it in GitHub Desktop.
Save thurendous/a8bed9fe41abb6b8deff1af73acde8e9 to your computer and use it in GitHub Desktop.
About encodings in solidity and talking about how they work.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
// For the cheatsheet, check out the docs: https://docs.soliditylang.org/en/v0.8.13/cheatsheet.html?highlight=encodewithsignature
contract Encoding {
function combineStrings() public pure returns (string memory) {
return string(abi.encodePacked("Hi Mom! ", "Miss you."));
}
// When we send a transaction, it is "compiled" down to bytecode and sent in a "data" object of the transaction.
// That data object now governs how future transactions will interact with it.
// For example: https://etherscan.io/tx/0x112133a0a74af775234c077c397c8b75850ceb61840b33b23ae06b753da40490
// Now, in order to read and understand these bytes, you need a special reader.
// This is supposed to be a new contract? How can you tell?
// Let's compile this contract in hardhat or remix, and you'll see the the "bytecode" output - that's that will be sent when
// creating a contract.
// This bytecode represents exactly the low level computer instructions to make our contract happen.
// These low level instructions are spread out into soemthing call opcodes.
// An opcode is going to be 2 characters that represents some special instruction, and also optionally has an input
// You can see a list of there here:
// https://www.evm.codes/
// Or here:
// https://github.com/crytic/evm-opcodes
// This opcode reader is sometimes abstractly called the EVM - or the ethereum virtual machine.
// The EVM basically represents all the instructions a computer needs to be able to read.
// Any language that can compile down to bytecode with these opcodes is considered EVM compatible
// Which is why so many blockchains are able to do this - you just get them to be able to understand the EVM and presto! Solidity smart contracts work on those blockchains.
// Now, just the binary can be hard to read, so why not press the `assembly` button? You'll get the binary translated into
// the opcodes and inputs for us!
// We aren't going to go much deeper into opcodes, but they are important to know to understand how to build more complex apps.
// How does this relate back to what we are talking about?
// Well let's look at this encoding stuff
// In this function, we encode the number one to what it'll look like in binary
// Or put another way, we ABI encode it.
function encodeNumber() public pure returns (bytes memory) {
bytes memory number = abi.encode(1);
return number;
}
// You'd use this to make calls to contracts
function encodeString() public pure returns (bytes memory) {
bytes memory someString = abi.encode("some string");
return someString;
}
// https://forum.openzeppelin.com/t/difference-between-abi-encodepacked-string-and-bytes-string/11837
// encodePacked
// This is great if you want to save space, not good for calling functions.
// You can sort of think of it as a compressor for the massive bytes object above.
function encodeStringPacked() public pure returns (bytes memory) {
bytes memory someString = abi.encodePacked("some string");
return someString;
}
// This is just type casting to string
// It's slightly different from below, and they have different gas costs
function encodeStringBytes() public pure returns (bytes memory) {
bytes memory someString = bytes("some string");
return someString;
}
function decodeString() public pure returns (string memory) {
string memory someString = abi.decode(encodeString(), (string));
return someString;
}
function multiEncode() public pure returns (bytes memory) {
bytes memory someString = abi.encode("some string", "it's bigger!");
return someString;
}
// Gas: 24612
function multiDecode() public pure returns (string memory, string memory) {
(string memory someString, string memory someOtherString) = abi.decode(
multiEncode(),
(string, string)
);
return (someString, someOtherString);
}
function multiEncodePacked() public pure returns (bytes memory) {
bytes memory someString = abi.encodePacked(
"some string",
"it's bigger!"
);
return someString;
}
// This doesn't work!
function multiDecodePacked() public pure returns (string memory) {
string memory someString = abi.decode(multiEncodePacked(), (string));
return someString;
}
// This does!
// Gas: 22313
function multiStringCastPacked() public pure returns (string memory) {
string memory someString = string(multiEncodePacked());
return someString;
}
// As of 0.8.13, you can now do `string.concat(string1, string2)`
// This abi.encoding stuff seems a little hard just to do string concatenation... is this for anything else?
// Why yes, yes it is.
// Since we know that our solidity is just going to get compiled down to this binary stuff to send a transaction...
// We could just use this superpower to send transactions to do EXACTLY what we want them to do...
// Remeber how before I said you always need two things to call a contract:
// 1. ABI
// 2. Contract Address?
// Well... That was true, but you don't need that massive ABI file. All we need to know is how to create the binary to call
// the functions that we want to call.
// Solidity has some more "low-level" keywords, namely "staticcall" and "call". We've used call in the past, but
// haven't really explained what was going on. There is also "send"... but basically forget about send.
// call: How we call functions to change the state of the blockchain.
// staticcall: This is how (at a low level) we do our "view" or "pure" function calls, and potentially don't change the blockchain state.
// When you call a function, you are secretly calling "call" behind the scenes, with everything compiled down to the binary stuff
// for you. Flashback to when we withdrew ETH from our raffle:
function withdraw(address recentWinner) public {
(bool success, ) = recentWinner.call{value: address(this).balance}("");
require(success, "Transfer Failed");
}
// Remember this?
// - In our {} we were able to pass specific fields of a transaction, like value.
// - In our () we were able to pass data in order to call a specific function - but there was no function we wanted to call!
// We only sent ETH, so we didn't need to call a function!
// If we want to call a function, or send any data, we'd do it in these parathesis!
// Let's look at another contract to explain this more...
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
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(
abi.encodeWithSelector(getSelectorOne(), 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);
}
// a bunch of different ways to get selector
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
contract Encoding {
function combineStrings() public pure returns(string memory) {
return string(abi.encodePacked("Hi mom, ", "miss you!"));
}
function combineStrings2() public pure returns(string memory) {
return string.concat("Hi Mom", "miss you!!");
}
function encodeNumber() public pure returns(bytes memory) {
bytes memory number = abi.encode(1);
return number;
}
function encodeString() public pure returns (bytes memory) {
bytes memory someString = abi.encode("aaaa");
return someString;
}
function encodeStringPacked() public pure returns(bytes memory) {
bytes memory someString = abi.encodePacked("aaaa");
return someString;
}
function encodeStringBytes() public pure returns (bytes memory) {
return bytes("aaaa");
}
function decodeString() public pure returns(string memory) {
string memory someString = abi.decode(encodeString(), (string));
return someString;
}
function multiEncode() public pure returns(bytes memory) {
bytes memory someString = abi.encode("some string.", "it's bigger!");
return someString;
}
function multiDecode(bytes memory code) public pure returns(string memory, string memory) {
(string memory someString, string memory someOtherString) = abi.decode(code, (string, string));
return (someString, someOtherString);
}
function multiEncodePacked() public pure returns(bytes memory) {
bytes memory someString = abi.encodePacked("some string", "it's bigger!!!");
return someString;
}
// this won't work
function multiDecodePacked() public pure returns(string memory) {
string memory someString = abi.decode(multiEncodePacked(), (string));
return someString;
}
function multiStringcastPacked() public pure returns(string memory) {
string memory someString = string(multiEncodePacked());
return someString;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment