Etherscan – Discrepancy Between Opcode View and Opcode Disassembler for USDT Contract

bytecodecontract-debuggingetherscanevmopcode

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.

Best Answer

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.

Related Topic