1

We have USDT smart contract, which is located at 0xdac17f958d2ee523a2206206994597c13d831ec7. I am trying to decompile the smart contract (at least view it as at opcodes). What I see is that it begins with 60606040526000 (ABI on etherscan)

[0] PUSH1 0x60 -> 6060
[2] PUSH1 0x40 -> 6040
[4] MSTORE -> 52
[5] PUSH1 0x00 -> 6000

and so says opcode tool, but if you Switch to opcode view on contract page you will see the following opcodes:

PUSH1 0x60
PUSH1 0x40
MSTORE
PUSH1 0x04
CALLDATASIZE
LT
PUSH2 0x0196
JUMPI
PUSH1 0x00
CALLDATALOAD

which are not

# from opcode tool

[1] PUSH1 0x60
[3] PUSH1 0x40
[4] MSTORE
[6] PUSH1 0x00
[7] DUP1
[9] PUSH1 0x14
[12] PUSH2 0x0100
[13] EXP
[14] DUP2
[15] SLOAD
[16] DUP2

the question is why do those differ? And what is the right one? I guess the second variant is right due to the docs, but can etherscan mistake? By the way, if you know any docs on compiled smart contract structure it'd be nice to share, so I and everyone could understand it better.

1 Answer 1

2

Your "opcode tool" link leads to a completely different address (0x9e1b57fc92eba6434251a8458811c32690f32c45). If you check opcodes for your original address, you'll see they're the same:

  • 0xdac17f958d2ee523a2206206994597c13d831ec7 (code)
    PUSH1 0x60
    PUSH1 0x40
    MSTORE
    PUSH1 0x04
    CALLDATASIZE
    LT
    PUSH2 0x0196
    JUMPI
    PUSH1 0x00
    CALLDATALOAD
    PUSH29 0x0100000000000000000000000000000000000000000000000000000000
    SWAP1
    DIV
    ...
    
  • 0xdac17f958d2ee523a2206206994597c13d831ec7 (disassembler)
    [1] PUSH1 0x60
    [3] PUSH1 0x40
    [4] MSTORE
    [6] PUSH1 0x04
    [7] CALLDATASIZE
    [8] LT
    [11] PUSH2 0x0196
    [12] JUMPI
    [14] PUSH1 0x00
    [15] CALLDATALOAD
    [45] PUSH29 0x0100000000000000000000000000000000000000000000000000000000
    [46] SWAP1
    [47] DIV 
    ...
    

By the way, if you know any docs on compiled smart contract structure it'd be nice to share, so I and everyone could understand it better.

Check out this multi-part article series from OpenZeppelin: Deconstructing a Solidity Contract —Part I: Introduction.

Note that this just describes the bytecode produced by the Solidity compiler. Currently EVM does not enforce any structure so different compilers could do it differently. All EVM does is start executing the binary blob at position 0 and go wherever the jumps take it. Some parts of the binary might never be executed - you can for example just append random junk to any valid bytecode and it will remain valid (and that part won't be executed because there can be no jumps to it). solc uses this fact to create code/data sections (sub-assemblies/sub-objects) and add metadata hash at the end. For example the runtime code to be deployed is a sub-assembly. If your contract deploys other contracts with new, the bytecode of each of these contracts also gets a seperate sub-assembly.

This free-form structure has its downsides and is going to change in the future. See EIP-3540: EVM Object Format (EOF) v1, which is a new standard that will make it more rigid.

1
  • Thanks for the explanation!
    – Ivan
    Jan 9 at 5:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.