-
Notifications
You must be signed in to change notification settings - Fork 28
/
Encoding.sol
140 lines (110 loc) · 6.58 KB
/
Encoding.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
// 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...
}