Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fork not working as expected #20

Open
elyase opened this issue May 10, 2024 · 4 comments
Open

Fork not working as expected #20

elyase opened this issue May 10, 2024 · 4 comments

Comments

@elyase
Copy link

elyase commented May 10, 2024

Problem:

This tx simulates on tenderly or cast without issue but fails with pyrevm (see below for tx details). There are no other txs for that pool in the block.

Code

from pyrevm import EVM

# Swap 0.031485012992133186 ETH for 1,110.686711078457978839 CSWAP on Uniswap v2
block_number = 19726294
caller = "0xc0170e050355408e53800d093f3f13c3a53b29c8"
router = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
value = 31485012992133186
tx_input = bytes.fromhex(
    "b6f9de950000000000000000000000000000000000000000000000343cc08b07804e00000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000c0170e050355408e53800d093f3f13c3a53b29c80000000000000000000000000000000000000000000000000000000066292d4e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000ae41b275aaaf484b541a5881a2dded9515184cca"
)

fork_url = "https://eth.merkle.io"
evm = EVM(
    fork_url=fork_url,
    fork_block=str(block_number - 1),
)

evm.set_balance(caller, value)
evm.message_call(
    caller=caller,
    to=router,
    calldata=tx_input,
    value=value,
    is_static=False,
)

Error

it fails with:

evm.message_call(
RuntimeError: Revert { gas_used: 221178, output: 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002b556e69737761705632526f757465723a20494e53554646494349454e545f4f55545055545f414d4f554e54000000000000000000000000000000000000000080 }

Error can be decoded to UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT

Transaction details

cast tx --rpc-url "https://eth.merkle.io" 0x652bae3b1c11fbcf64309c2dfec5537b9bd1d3f2b521176f71ba1115a313ecbd --json | jq .
{
  "hash": "0x652bae3b1c11fbcf64309c2dfec5537b9bd1d3f2b521176f71ba1115a313ecbd",
  "nonce": "0xf4",
  "blockHash": "0x46f9451a299c23b82ce71b0d4c510ebddad5a7b6830237ed4e08440ff8985715",
  "blockNumber": "0x12cffd6",
  "transactionIndex": "0xf",
  "from": "0xc0170e050355408e53800d093f3f13c3a53b29c8",
  "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
  "value": "0x6fdb73d7250842",
  "gasPrice": "0x4902e7d7f",
  "gas": "0x300d8",
  "input": "0xb6f9de950000000000000000000000000000000000000000000000343cc08b07804e00000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000c0170e050355408e53800d093f3f13c3a53b29c80000000000000000000000000000000000000000000000000000000066292d4e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000ae41b275aaaf484b541a5881a2dded9515184cca",
  "v": "0x0",
  "r": "0xe4b274dbd9d9412fa10821d6234dc40212d95022a71ad6986cd84030aeda1e8a",
  "s": "0x44a3d544bb513107be06c1da4f39820dc72cc49a1cf076d42abb27ee5ee4e401",
  "sourceHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "isSystemTx": false,
  "type": "0x2",
  "accessList": [],
  "maxPriorityFeePerGas": "0x3b9aca00",
  "maxFeePerGas": "0x8a5215ece",
  "chainId": "0x1",
  "yParity": "0x0"
}
@DanielVF
Copy link

DanielVF commented Aug 20, 2024

Gathered some more data on this:

The CSWAP token is a fee on transfer token. When transferred, it also sends itself a portion of then funds.

In the real transaction on mainnet (that worked), the user received above the minimum, and the router did not revert.

I've verified that the previous transfer (anywhere on mainnet) was in block 19,726,290, and the next transfer was in block 19,726,299, meaning this TX in 19,726,294 didn't have other transfers of that coin in it, nor in adjacent blocks around it.

The fee on transfer Uniswap router function works by checking the user's before balance, against their balance after the swap. The initial user CSWAP balanceOf perfectly match in both the revm fork sim and the live transaction. However the after balance in fork is far lower, indicating that a much smaller amount transfer.

Amount What Where
963.608343535034433536 min out amount of cswap expected mainnet
1,110.686711078457978839 transferred by the pool mainnet
1,055.152375524535079898 user received after fees mainnet
11.106867110784579789 user received after fees fork

Oddly enough, multiplying the value passed in by 100x makes things within 1% of the correct output value.

✅ Verified that the initial storage slots for the Uniswap 2 pools balances match exactly.

@DanielVF
Copy link

Note that this fee on transfer token is not a simple fee on transfer token - it has a lot of logic with differences depending on block numbers, and stored block numbers. It also explicitly tries to trap bots.

When comparing storage slot reads between the live contract and the fork, the first difference I notice happens during the token transfer. The automatedMarketMakerPairs['to'] and automatedMarketMakerPairs['from'] are SLOADed in reversed order. The values read are correct in both cases.

image image

@DanielVF
Copy link

The difference in storage load orders happens because the function earlyBuyPenaltyInEffect returns a different value.

image

Turns out the blocknumber is opcode is returning a zero. Tiny bit more investigating and evm.env.block.number is actually returning a zero.

When I force set the correct block number when setting up the EVM, the example transaction works perfectly.

evm = EVM(
    fork_url=fork_url,
    fork_block=str(block_number-1),
    tracing=True,
    env=Env(
        block=BlockEnv(number=block_number)
    ),
)

I would think we would want to change the pyrevm to set the block number automatically, if the block number is available from the forked block.

Great Success.

image

@elyase
Copy link
Author

elyase commented Aug 20, 2024

Wow, great detective work! Thank you @DanielVF!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants