-
Notifications
You must be signed in to change notification settings - Fork 28
/
CallAnything.sol
120 lines (100 loc) · 5.34 KB
/
CallAnything.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
// SPDX-License-Identifier: MIT
// So why do we care about all this encoding stuff?
// In order to call a function using only the data field of call, we need to encode:
// The function name
// The parameters we want to add
// Down to the binary level
// Now each contract assigns each function it has a function ID. This is known as the "function selector".
// The "function selector" is the first 4 bytes of the function signature.
// The "function signature" is a string that defines the function name & parameters.
// Let's look at this
pragma solidity 0.8.20;
contract CallAnything {
address public s_someAddress;
uint256 public s_amount;
function transfer(address someAddress, uint256 amount) public {
// Some code
s_someAddress = someAddress;
s_amount = amount;
}
// We can get a function selector as easy as this.
// "transfer(address,uint256)" is our function signature
// and our resulting function selector of "transfer(address,uint256)" is output from this function
// one thing to note here is that there shouldn't be any spaces in "transfer(address,uint256)"
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);
}
// So... How can we use the selector to call our transfer function now then?
function callTransferFunctionDirectly(address someAddress, uint256 amount) public returns (bytes4, bool) {
(bool success, bytes memory returnData) = address(this).call(
// getDataToCallTransfer(someAddress, amount);
abi.encodeWithSelector(getSelectorOne(), someAddress, amount)
);
return (bytes4(returnData), success);
}
// Using encodeWithSignature
function callTransferFunctionDirectlyTwo(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);
}
// We can also get a function selector from data sent into the call
function getSelectorTwo() public view returns (bytes4 selector) {
bytes memory functionCallData = abi.encodeWithSignature("transfer(address,uint256)", address(this), 123);
selector =
bytes4(bytes.concat(functionCallData[0], functionCallData[1], functionCallData[2], functionCallData[3]));
}
// Another way to get data (hard coded)
function getCallData() public view returns (bytes memory) {
return abi.encodeWithSignature("transfer(address,uint256)", address(this), 123);
}
// Pass this:
// 0xa9059cbb000000000000000000000000d7acd2a9fd159e69bb102a1ca21c9a3e3a5f771b000000000000000000000000000000000000000000000000000000000000007b
// This is output of `getCallData()`
// This is another low level way to get function selector using assembly
// You can actually write code that resembles the opcodes using the assembly keyword!
// This in-line assembly is called "Yul"
// It's a best practice to use it as little as possible - only when you need to do something very VERY specific
function getSelectorThree(bytes calldata functionCallData) public pure returns (bytes4 selector) {
// offset is a special attribute of calldata
assembly {
selector := calldataload(functionCallData.offset)
}
}
// Another way to get your selector with the "this" keyword
function getSelectorFour() public pure returns (bytes4 selector) {
return this.transfer.selector;
}
// Just a function that gets the signature
function getSignatureOne() public pure returns (string memory) {
return "transfer(address,uint256)";
}
}
contract CallFunctionWithoutContract {
address public s_selectorsAndSignaturesAddress;
constructor(address selectorsAndSignaturesAddress) {
s_selectorsAndSignaturesAddress = selectorsAndSignaturesAddress;
}
// pass in 0xa9059cbb000000000000000000000000d7acd2a9fd159e69bb102a1ca21c9a3e3a5f771b000000000000000000000000000000000000000000000000000000000000007b
// you could use this to change state
function callFunctionDirectly(bytes calldata callData) public returns (bytes4, bool) {
(bool success, bytes memory returnData) =
s_selectorsAndSignaturesAddress.call(abi.encodeWithSignature("getSelectorThree(bytes)", callData));
return (bytes4(returnData), success);
}
// with a staticcall, we can have this be a view function!
function staticCallFunctionDirectly() public view returns (bytes4, bool) {
(bool success, bytes memory returnData) =
s_selectorsAndSignaturesAddress.staticcall(abi.encodeWithSignature("getSelectorOne()"));
return (bytes4(returnData), success);
}
function callTransferFunctionDirectlyThree(address someAddress, uint256 amount) public returns (bytes4, bool) {
(bool success, bytes memory returnData) = s_selectorsAndSignaturesAddress.call(
abi.encodeWithSignature("transfer(address,uint256)", someAddress, amount)
);
return (bytes4(returnData), success);
}
}