How To Decipher A Smart Contract Method Call

Diving Into The Ethereum VM Part 4

Contract Transaction

pragma solidity ^0.4.11;contract C {
uint256 a;
function setA(uint256 _a) {
a = _a;
}
function getA() returns(uint256) {
return a;
}
}
0xee919d500000000000000000000000000000000000000000000000000000000000000001
# The method selector (4 bytes)
0xee919d5
# The 1st argument (32 bytes)
00000000000000000000000000000000000000000000000000000000000000001
# Install pyethereum https://github.com/ethereum/pyethereum/#installation
> from ethereum.utils import sha3
> sha3("setA(uint256)").hex()
'ee919d50445cd9f463621849366a537968fe1ce096894b0d0c001528383d4769'
> sha3("setA(uint256)")[0:8].hex()
'ee919d50'

The Application Binary Interface (ABI)

Calling A Getter

>>> sha3("getA()")[0:8].hex()
'd46300fd'
$ curl -X POST \
-H "Content-Type: application/json" \
"https://rinkeby.infura.io/YOUR_INFURA_TOKEN" \
--data '
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [
{
"to": "0x62650ae5c5777d1660cc17fcd4f48f6a66b9a4c2",
"data": "0xd46300fd"
},
"latest"
]
}
'
{
"jsonrpc":"2.0",
"id":1,
"result":"0x0000000000000000000000000000000000000000000000000000000000000001"
}

Assembly For External Method Calling

pragma solidity ^0.4.11;contract C {
uint256 a;
// Note: `payable` makes the assembly a bit simpler
function setA(uint256 _a) payable {
a = _a;
}
}
solc --bin --asm --optimize call.sol
sub_0: assembly {
mstore(0x40, 0x60)
and(div(calldataload(0x0), 0x100000000000000000000000000000000000000000000000000000000), 0xffffffff)
0xee919d50
dup2
eq
tag_2
jumpi
tag_1:
0x0
dup1
revert
tag_2:
tag_3
calldataload(0x4)
jump(tag_4)
tag_3:
stop
tag_4:
/* "call.sol":95:96 a */
0x0
/* "call.sol":95:101 a = _a */
dup2
swap1
sstore
tag_5:
pop
jump // out
auxdata: 0xa165627a7a7230582016353b5ec133c89560dea787de20e25e96284d67a632e9df74dd981cc4db7a0a0029
}
// Load the first 4 bytes as method selector
and(div(calldataload(0x0), 0x100000000000000000000000000000000000000000000000000000000), 0xffffffff)
// if selector matches `0xee919d50`, goto setA
0xee919d50
dup2
eq
tag_2
jumpi
// No matching method. Fail & revert.
tag_1:
0x0
dup1
revert
// Body of setA
tag_2:
...
methodSelector = calldata[0:4]if methodSelector == "0xee919d50":
goto tag_2 // goto setA
else:
// No matching method. Fail & revert.
revert
// setA
tag_2:
// Where to goto after method call
tag_3
// Load first argument (the value 0x1).
calldataload(0x4)
// Execute method.
jump(tag_4)
tag_4:
// sstore(0x0, 0x1)
0x0
dup2
swap1
sstore
tag_5:
pop
// end of program, will goto tag_3 and stop
jump
tag_3:
// end of program
stop
// Saves the position to return to after method call.
@returnTo = tag_3
tag_2: // setA
// Loads the arguments from call data onto the stack.
@arg1 = calldata[4:4+32]
tag_4: // a = _a
sstore(0x0, @arg1)
tag_5 // return
jump(@returnTo)
tag_3:
stop
methodSelector = calldata[0:4]if methodSelector == "0xee919d50":
goto tag_2 // goto setA
else:
// No matching method. Fail.
revert
@returnTo = tag_3
tag_2: // setA(uint256 _a)
@arg1 = calldata[4:36]
tag_4: // a = _a
sstore(0x0, @arg1)
tag_5 // return
jump(@returnTo)
tag_3:
stop

Handling Multiple Methods

pragma solidity ^0.4.11;contract C {
uint256 a;
uint256 b;
function setA(uint256 _a) {
a = _a;
}
function setB(uint256 _b) {
b = _b;
}
}
// methodSelector = calldata[0:4]
and(div(calldataload(0x0), 0x100000000000000000000000000000000000000000000000000000000), 0xffffffff)
// if methodSelector == 0x9cdcf9b
0x9cdcf9b
dup2
eq
tag_2 // SetB
jumpi
// elsif methodSelector == 0xee919d50
dup1
0xee919d50
eq
tag_3 // SetA
jumpi
methodSelector = calldata[0:4]if methodSelector == "0x9cdcf9b":
goto tag_2
elsif methodSelector == "0xee919d50":
goto tag_3
else:
// Cannot find a matching method. Fail.
revert

ABI Encoding For Complex Method Calls

Don’t worry about the zeros. It’s FINE.
from ethereum.abi import encode_abi
# The first array lists the types of the arguments.
# The second array lists the argument values.
> encode_abi(["uint256", "uint256", "uint256"],[1, 2, 3]).hex()
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003
> encode_abi(["int8", "uint32", "uint64"],[1, 2, 3]).hex()0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003
> encode_abi(
["int8[3]", "int256[3]"],
[[1, 2, 3], [4, 5, 6]]
).hex()
// int8[3]. Zero-padded to 32 bytes.
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003
// int256[3].
0000000000000000000000000000000000000000000000000000000000000004
0000000000000000000000000000000000000000000000000000000000000005
0000000000000000000000000000000000000000000000000000000000000006

ABI Encoding for Dynamic Arrays

> encode_abi(
["uint256[]", "uint256[]", "uint256[]"],
[[0xa1, 0xa2, 0xa3], [0xb1, 0xb2, 0xb3], [0xc1, 0xc2, 0xc3]]
).hex()
/************* HEAD (32*3 bytes) *************/
// arg1: look at position 0x60 for array data
0000000000000000000000000000000000000000000000000000000000000060
// arg2: look at position 0xe0 for array data
00000000000000000000000000000000000000000000000000000000000000e0
// arg3: look at position 0x160 for array data
0000000000000000000000000000000000000000000000000000000000000160
/************* TAIL (128**3 bytes) *************/
// position 0x60. Data for arg1.
// Length followed by elements.
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000a1
00000000000000000000000000000000000000000000000000000000000000a2
00000000000000000000000000000000000000000000000000000000000000a3
// position 0xe0. Data for arg2.
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000b1
00000000000000000000000000000000000000000000000000000000000000b2
00000000000000000000000000000000000000000000000000000000000000b3
// position 0x160. Data for arg3.
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000c1
00000000000000000000000000000000000000000000000000000000000000c2
00000000000000000000000000000000000000000000000000000000000000c3
> encode_abi(
["uint256", "uint256[]", "uint256"],
[0xaaaa, [0xb1, 0xb2, 0xb3], 0xbbbb]
).hex()
/************* HEAD (32*3 bytes) *************/
// arg1: 0xaaaa
000000000000000000000000000000000000000000000000000000000000aaaa
// arg2: look at position 0x60 for array data
0000000000000000000000000000000000000000000000000000000000000060
// arg3: 0xbbbb
000000000000000000000000000000000000000000000000000000000000bbbb
/************* TAIL (128 bytes) *************/
// position 0x60. Data for arg2.
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000b1
00000000000000000000000000000000000000000000000000000000000000b2
00000000000000000000000000000000000000000000000000000000000000b3

Encoding Bytes

> encode_abi(
["string", "string", "string"],
["aaaa", "bbbb", "cccc"]
).hex()
// arg1: look at position 0x60 for string data
0000000000000000000000000000000000000000000000000000000000000060
// arg2: look at position 0xa0 for string data
00000000000000000000000000000000000000000000000000000000000000a0
// arg3: look at position 0xe0 for string data
00000000000000000000000000000000000000000000000000000000000000e0
// 0x60 (96). Data for arg1
0000000000000000000000000000000000000000000000000000000000000004
6161616100000000000000000000000000000000000000000000000000000000
// 0xa0 (160). Data for arg2
0000000000000000000000000000000000000000000000000000000000000004
6262626200000000000000000000000000000000000000000000000000000000
// 0xe0 (224). Data for arg3
0000000000000000000000000000000000000000000000000000000000000004
6363636300000000000000000000000000000000000000000000000000000000
// encode 48 bytes of string data
ethereum.abi.encode_abi(
["string"],
["a" * (32+16)]
).hex()

0000000000000000000000000000000000000000000000000000000000000020
// length of string is 0x30 (48)
0000000000000000000000000000000000000000000000000000000000000030
6161616161616161616161616161616161616161616161616161616161616161
6161616161616161616161616161616100000000000000000000000000000000

Nested Arrays

> encode_abi(
["uint256[][]"],
[[[0xa1, 0xa2, 0xa3], [0xb1, 0xb2, 0xb3], [0xc1, 0xc2, 0xc3]]]
).hex()
// arg1: The outter array is at position 0x20.
0000000000000000000000000000000000000000000000000000000000000020
// 0x20. Each element is the position of an inner array.
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000060
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000160
// array[0] at 0x60
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000a1
00000000000000000000000000000000000000000000000000000000000000a2
00000000000000000000000000000000000000000000000000000000000000a3
// array[1] at 0xe0
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000b1
00000000000000000000000000000000000000000000000000000000000000b2
00000000000000000000000000000000000000000000000000000000000000b3
// array[2] at 0x160
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000c1
00000000000000000000000000000000000000000000000000000000000000c2
00000000000000000000000000000000000000000000000000000000000000c3

Gas Cost & ABI Encoding Design

Negative Integers…

ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

Conclusion