Uniswap / examples

107 stars 141 forks source link

Example trade WETH/USDC fails with `STF` error on mainnet fork #58

Open vdamle opened 7 months ago

vdamle commented 7 months ago

I've been attempting to get the example trade to work using a mainnet fork setup with Anvil, as documented here

As far as I can tell, the example doesn't work with the latest version of the libraries:

    "@uniswap/sdk-core": "^4.0.9",
    "@uniswap/v3-sdk": "^3.10.0",

The error is always:

eth_sendRawTransaction

    Transaction: 0x37640ca5d41ca1e7264fff50678cac102cb88ccc20d002c72ef3355b275fe38c
    Gas used: 115902
    Error: reverted with: revert: STF

    Block Number: 18642983
    Block Hash: 0x31395f673ef6640d4e45273ac643840c9f637c9b5ed826c3d6b1e36c295bf5a9
    Block Time: "Fri, 24 Nov 2023 21:25:20 +0000"

Initially, I was seeing an error with eth_estimateGas:

reason: 'cannot estimate gas; transaction may fail or may require manual gas limit',
  code: 'UNPREDICTABLE_GAS_LIMIT'

So I switched to using a pre-defined gasLimit: 5_000_000 in the Tx.

The steps for getting a quote and approving the swap router for token transfers work but the actual swap fails. As a minor detail, I'm not using the UI code but invoking the functions directly like below, so I can run the example from the command line:

async function performTrade() {
  let t = await createTrade();

  let res = await executeTrade(t);

  console.log(`Trade concluded, ${res}`);
}

performTrade()
  .then(() => console.log(`Complete`))
  .catch((e) => console.error(`Error executing trade`, e));

I can see that the quote returns valid data:

Token0 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
Token1 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Fee 3000
Tick spacing 60
Sqrt PriceX96 BigNumber {
  _hex: '0x55528bdd18dd0fa512bb36b96ace',
  _isBigNumber: true
}
Tick 19984

I'd appreciate any input to troubleshoot this issue. I'd be happy to submit a PR to the docs/example code if there is any change required with the latest version of the v3 SDK.

vdamle commented 7 months ago

To verify that the approval is correctly done, I wrote a script to check the allowance:


import hre from 'hardhat';
import { JsonRpcProvider, MaxUint256, Wallet } from 'ethers';

const WETH_CONTRACT_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const WALLET_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
const SWAP_ROUTER_ADDRESS = '0xE592427A0AEce92De3Edee1F18E0157C05861564';
const SWAP_ROUTER_V2_ADDRESS = '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45';

const ERC20_ABI = [
    'function approve(address _spender,uint256 _value) returns (bool)',
    'function allowance(address _owner,address _spender) view returns (uint256)',
    'function balanceOf(address _owner) view returns (uint256)',
    'function totalSupply() view returns (uint256)'
]
async function main() {

    const rpc =  new JsonRpcProvider( 'http://localhost:8545' ) ;
    const wallet = new Wallet( '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' , rpc);

  const weth_contract = new hre.ethers.Contract(WETH_CONTRACT_ADDRESS, ERC20_ABI, wallet);

  console.log(`checking allowance for ${SWAP_ROUTER_ADDRESS}`)
  const allowance = await weth_contract.allowance(WALLET_ADDRESS, SWAP_ROUTER_ADDRESS);

  console.log(`Allowance: ${allowance}`);
}

main()
  .then(() => console.log('Execution complete'))
  .catch((e) => console.error('unable to run hardhat script', e));

and I see that the swap router allowance is correctly resported:

➜  hardhat-scripts node scripts/allowance.js
checking allowance for 0xE592427A0AEce92De3Edee1F18E0157C05861564
Allowance: 2000000000000000000000
Execution complete
69popovvlad commented 6 months ago

Same thing, I also got an error with eth_estimateGas at first But now the STF error

@vdamle did you solve the problem?

animalconcerts commented 4 months ago

@vdamle @69popovvlad I have the same problem. Did you guys solve?

69popovvlad commented 4 months ago

@vdamle @69popovvlad I have the same problem. Did you guys solve?

@animalconcerts Yes, I somehow solved it, but I don’t remember exactly how, because I solved several problems that kept popping up:

Here is my code below as an example for you: TransactionState.ts

    export enum TransactionState {
        Failed = 'Failed',
        New = 'New',
        Rejected = 'Rejected',
        Sending = 'Sending',
        Sent = 'Sent',
    }

Some utils script

    import { Wallet, ethers } from "ethers";
    import { Token } from "@uniswap/sdk-core";
    import ERC20 from '@eth-optimism/contracts-bedrock/forge-artifacts/ERC20.sol/ERC20.json' assert { type: 'json' };

    /**
     * @param tokenAdress Get ERC20 token contract
     * @param wallet The wallet for which you need to find out the token balance
     * @returns Signed contract of the ERC20 token
     */
    export function getTokenContractERC20(tokenAdress: string, wallet: Wallet): Contract {
        return new ethers.Contract(tokenAdress, ERC20.abi, wallet);
    }

    export function tokenAmountToWei(token: Token, amount: number) {
        return ethers.parseUnits(amount.toString(), token.decimals);
    }

WalletApi.ts

    import { Contract, Wallet, ethers } from "ethers";
    import { Token } from "@uniswap/sdk-core";
    import { TransactionState } from "./TransactionState.js";

async approveForUniswap(token0: Token, token1: Token, wallet: Wallet, amount: number) {
    const amountToken0 = amount < 0 ? ethers.MaxUint256 : tokenAmountToWei(token0, amount);
    const amountToken1 = amount < 0 ? ethers.MaxUint256 : tokenAmountToWei(token1, amount);

    const tx0 = await approve(SWAP_ROUTER_ADDRESS, token0, amountToken0, wallet);
    const tx1 = await approve(SWAP_ROUTER_ADDRESS, token1, amountToken1, wallet);
    const tx2 = await approve(NONFUNGIBLE_POSITION_ADDRESS, token0, amountToken0, wallet);
    const tx3 = await approve(NONFUNGIBLE_POSITION_ADDRESS, token1, amountToken1, wallet);

    return tx0 == tx1 && tx2 == tx3 && tx0 == tx3 && tx0 == TransactionState.Sent ? TransactionState.Sent : TransactionState.Failed;
}

async approve(spenderAddress: string, token: Token, amountWei: bigint, wallet: Wallet) {
    try {
        const tokenContract = getTokenContractERC20(token.address, wallet);

        let allowancewei = await tokenContract.allowance.staticCall(
            wallet.address,
            spenderAddress,
        );

        if (amountWei <= allowancewei) {
            return TransactionState.Sent;
        }

        const tx = await tokenContract.approve(
            spenderAddress,
            ethers.MaxUint256
        );
        await tx.wait();

        allowancewei = await tokenContract.allowance.staticCall(
            wallet.address,
            spenderAddress,
        );

        if (amountWei > allowancewei) {
            throw new Error(`Allowance amount of tokens ${allowancewei} is less than the required amount ${amountWei}`);
        }

        return TransactionState.Sent;
    }
    catch (error) {
        console.error(`Approve error:: ${error}`);
        return TransactionState.Failed;
    }
}

My dependencies btw

"dependencies": {
    "@eth-optimism/contracts-bedrock": "0.16.2",
    "@uniswap/sdk-core": "4.0.9",
    "@uniswap/v3-core": "1.0.1",
    "@uniswap/v3-periphery": "1.4.4",
    "@uniswap/v3-sdk": "3.10.0",
    "ethers": "6.9.0",
    "jsbi": "3.2.5"
},