3

So, first off, I have checked every single question on this topic, and it seems to come down to the particulars of the code. I can't find a throughline for any of them.

That said, I have a bot that is signing and sending blockchain transactions on behalf of an arbitrage contract. However, when I try to sign and send a transaction, I get the error stack below. What's going wrong and how can I fix this?

The code:

  if (config.PROJECT_SETTINGS.isDeployed) {
      const tradeData = await arbitrage.methods.executeTrade(startOnUniswap, _token0Contract._address, _token1Contract._address, payOut).encodeABI();
      const nonce = await web3.eth.getTransactionCount(account);
      const gasPrice = await web3.eth.getGasPrice();
      const gasLimit = 200000;
      const rawTransaction = {
         from: account,
         nonce: web3.utils.toHex(nonce),
         gasPrice: web3.utils.toHex(gasPrice),
         gasLimit: web3.utils.toHex(gasLimit),
         to: arbitrage._address,
         data: tradeData,
                };
const signedTransaction = await web3.eth.accounts.signTransaction(rawTransaction, process.env.PRIVATE_KEY);
await web3.eth.sendSignedTransaction(signedTransaction.rawTransaction);
            }

The arbitrage contract:

// SPDX-License-Identifier: MIT
pragma solidity <=0.8.10;

import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface Structs {
    struct Val {
        uint256 value;
    }

    enum ActionType {
        Deposit, // supply tokens
        Withdraw, // borrow tokens
        Transfer, // transfer balance between accounts
        Buy, // buy an amount of some token (externally)
        Sell, // sell an amount of some token (externally)
        Trade, // trade tokens against another account
        Liquidate, // liquidate an undercollateralized or expiring account
        Vaporize, // use excess tokens to zero-out a completely negative account
        Call // send arbitrary data to an address
    }

    enum AssetDenomination {
        Wei // the amount is denominated in wei
    }

    enum AssetReference {
        Delta // the amount is given as a delta from the current value
    }

    struct AssetAmount {
        bool sign; // true if positive
        AssetDenomination denomination;
        AssetReference ref;
        uint256 value;
    }

    struct ActionArgs {
        ActionType actionType;
        uint256 accountId;
        AssetAmount amount;
        uint256 primaryMarketId;
        uint256 secondaryMarketId;
        address otherAddress;
        uint256 otherAccountId;
        bytes data;
    }

    struct Info {
        address owner; // The address that owns the account
        uint256 number; // A nonce that allows a single address to control many accounts
    }

    struct Wei {
        bool sign; // true if positive
        uint256 value;
    }
}

abstract contract DyDxPool is Structs {
    function getAccountWei(Info memory account, uint256 marketId)
        public
        view
        virtual
        returns (Wei memory);

    function operate(Info[] memory, ActionArgs[] memory) public virtual;
}

contract DyDxFlashLoan is Structs {
    DyDxPool pool = DyDxPool(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e);

    address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    mapping(address => uint256) public currencies;

    constructor() {
        currencies[WETH] = 1;
    }

    modifier onlyPool() {
        require(
            msg.sender == address(pool),
            "FlashLoan: could be called by DyDx pool only"
        );
        _;
    }

    function tokenToMarketId(address token) public view returns (uint256) {
        uint256 marketId = currencies[token];
        require(marketId != 0, "FlashLoan: Unsupported token");
        return marketId - 1;
    }

    // the DyDx will call `callFunction(address sender, Info memory accountInfo, bytes memory data) public` after during `operate` call
    function flashloan(
        address token,
        uint256 amount,
        bytes memory data
    ) internal {
        IERC20(token).approve(address(pool), amount + 1);
        Info[] memory infos = new Info[](1);
        ActionArgs[] memory args = new ActionArgs[](3);

        infos[0] = Info(address(this), 0);

        AssetAmount memory wamt = AssetAmount(
            false,
            AssetDenomination.Wei,
            AssetReference.Delta,
            amount
        );
        ActionArgs memory withdraw;
        withdraw.actionType = ActionType.Withdraw;
        withdraw.accountId = 0;
        withdraw.amount = wamt;
        withdraw.primaryMarketId = tokenToMarketId(token);
        withdraw.otherAddress = address(this);

        args[0] = withdraw;

        ActionArgs memory call;
        call.actionType = ActionType.Call;
        call.accountId = 0;
        call.otherAddress = address(this);
        call.data = data;

        args[1] = call;

        ActionArgs memory deposit;
        AssetAmount memory damt = AssetAmount(
            true,
            AssetDenomination.Wei,
            AssetReference.Delta,
            amount + 1
        );
        deposit.actionType = ActionType.Deposit;
        deposit.accountId = 0;
        deposit.amount = damt;
        deposit.primaryMarketId = tokenToMarketId(token);
        deposit.otherAddress = address(this);

        args[2] = deposit;

        pool.operate(infos, args);
    }
}

contract Arbitrage is DyDxFlashLoan {
    IUniswapV2Router02 public immutable sRouter;
    IUniswapV2Router02 public immutable uRouter;

    address public owner;

    constructor(address _sRouter, address _uRouter) {
        sRouter = IUniswapV2Router02(_sRouter); // Sushiswap
        uRouter = IUniswapV2Router02(_uRouter); // Uniswap
        owner = msg.sender;
    }

    function executeTrade(
        bool _startOnUniswap,
        address _token0,
        address _token1,
        uint256 _flashAmount
    ) external {
        uint256 balanceBefore = IERC20(_token0).balanceOf(address(this));

        bytes memory data = abi.encode(
            _startOnUniswap,
            _token0,
            _token1,
            _flashAmount,
            balanceBefore
        );

        flashloan(_token0, _flashAmount, data); // execution goes to `callFunction`
    }

    function callFunction(
        address, /* sender */
        Info calldata, /* accountInfo */
        bytes calldata data
    ) external onlyPool {
        (
            bool startOnUniswap,
            address token0,
            address token1,
            uint256 flashAmount,
            uint256 balanceBefore
        ) = abi.decode(data, (bool, address, address, uint256, uint256));

        uint256 balanceAfter = IERC20(token0).balanceOf(address(this));

        require(
            balanceAfter - balanceBefore == flashAmount,
            "contract did not get the loan"
        );

        // Use the money here!
        address[] memory path = new address[](2);

        path[0] = token0;
        path[1] = token1;

        if (startOnUniswap) {
            _swapOnUniswap(path, flashAmount, 0);

            path[0] = token1;
            path[1] = token0;

            _swapOnSushiswap(
                path,
                IERC20(token1).balanceOf(address(this)),
                (flashAmount + 1)
            );
        } else {
            _swapOnSushiswap(path, flashAmount, 0);

            path[0] = token1;
            path[1] = token0;

            _swapOnUniswap(
                path,
                IERC20(token1).balanceOf(address(this)),
                (flashAmount + 1)
            );
        }

        IERC20(token0).transfer(
            owner,
            IERC20(token0).balanceOf(address(this)) - (flashAmount + 1)
        );
    }

    // -- INTERNAL FUNCTIONS -- //

    function _swapOnUniswap(
        address[] memory _path,
        uint256 _amountIn,
        uint256 _amountOut
    ) internal {
        require(
            IERC20(_path[0]).approve(address(uRouter), _amountIn),
            "Uniswap approval failed."
        );

        uRouter.swapExactTokensForTokens(
            _amountIn,
            _amountOut,
            _path,
            address(this),
            (block.timestamp + 1200)
        );
    }

    function _swapOnSushiswap(
        address[] memory _path,
        uint256 _amountIn,
        uint256 _amountOut
    ) internal {
        require(
            IERC20(_path[0]).approve(address(sRouter), _amountIn),
            "Sushiswap approval failed."
        );

        sRouter.swapExactTokensForTokens(
            _amountIn,
            _amountOut,
            _path,
            address(this),
            (block.timestamp + 1200)
        );
    }
}

The error:

Error: Transaction has been reverted by the EVM:
{
  "blockHash": "0xeed5773a3228dd01bef285bbb3139f6d2170323058703e8cf30bc8ba6a927342",
  "blockNumber": 16632255,
  "contractAddress": null,
  "cumulativeGasUsed": 24687572,
  "effectiveGasPrice": 28667583352,
  "from": "0xdf58ca0a02bf9b43ce13b8ab5969e4b60f22c0a3",
  "gasUsed": 196577,
  "logs": [],
  "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "status": false,
  "to": "0x4a15775657e7720bbd9cb7039484ed0510be6689",
  "transactionHash": "0x5a748a0fd8c21898021823a424da94576f5d794ed9867bd62a7bd3027473ce00",
  "transactionIndex": 301,
  "type": "0x0"
}
    at Object.TransactionError (/Users/TrentKennelly/trading_bot_V2/node_modules/web3-core-helpers/lib/errors.js:87:21)
    at Object.TransactionRevertedWithoutReasonError (/Users/TrentKennelly/trading_bot_V2/node_modules/web3-core-helpers/lib/errors.js:98:21)
    at /Users/TrentKennelly/trading_bot_V2/node_modules/web3-core-method/lib/index.js:396:57
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  receipt: {
    blockHash: '0xeed5773a3228dd01bef285bbb3139f6d2170323058703e8cf30bc8ba6a927342',
    blockNumber: 16632255,
    contractAddress: null,
    cumulativeGasUsed: 24687572,
    effectiveGasPrice: 28667583352,
    from: '0xdf58ca0a02bf9b43ce13b8ab5969e4b60f22c0a3',
    gasUsed: 196577,
    logs: [],
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    status: false,
    to: '0x4a15775657e7720bbd9cb7039484ed0510be6689',
    transactionHash: '0x5a748a0fd8c21898021823a424da94576f5d794ed9867bd62a7bd3027473ce00',
    transactionIndex: 301,
    type: '0x0'
  }
}

I'm at a total loss as to what this could be. Any help is GREATLY appreciated!

EDIT: Thanks to the comment from Wuzhong, I've finally figured out how to at least understand the problem with this thing. Remix didn't work, even after verifying the contract (which is required for Remix to look at a transaction).

I ended up using truffle debug as detailed here. That got me to an error which I've seen before, but thought I fixed. So I'll ask about that now.

The error for both the transaction above and 0xb6242b8af50932cc975d180838a77f7b28b3fc4a8e963cfbca21e3fbaa30629d is as follows:

TrentKennelly@Trent-MBP-2022 trading_bot_v2 % truffle debug 0xb6242b8af50932cc975d180838a77f7b28b3fc4a8e963cfbca21e3fbaa30629d --network mainnet
Starting Truffle Debugger...
TypeError: Cannot create property 'gasLimit' on string '0xfdf159'
    at Method.outputBlockFormatter (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-helpers/lib/formatters.js:269:1)
    at Method.web3.eth.getBlock.method.outputFormatter (/usr/local/lib/node_modules/truffle/build/webpack:/packages/interface-adapter/dist/shim/overloads/ethereum.js:38:1)
    at Method.formatOutput (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-method/lib/index.js:148:1)
    at sendTxCallback (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-method/lib/index.js:536:1)
    at processTicksAndRejections (node:internal/process/task_queues:82:21)
Truffle v5.7.1 (core: 5.7.1)
Node v18.12.1
TrentKennelly@Trent-MBP-2022 trading_bot_v2 % truffle debug 0x5a748a0fd8c21898021823a424da94576f5d794ed9867bd62a7bd3027473ce00 --network mainnet
Starting Truffle Debugger...
TypeError: Cannot create property 'gasLimit' on string '0xfdf161'
    at Method.outputBlockFormatter (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-helpers/lib/formatters.js:269:1)
    at Method.web3.eth.getBlock.method.outputFormatter (/usr/local/lib/node_modules/truffle/build/webpack:/packages/interface-adapter/dist/shim/overloads/ethereum.js:38:1)
    at Method.formatOutput (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-method/lib/index.js:148:1)
    at sendTxCallback (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-method/lib/index.js:536:1)
    at processTicksAndRejections (node:internal/process/task_queues:82:21)

So... any thoughts on where this is going wrong?

2 Answers 2

1

For now, the Ethereum clients don't return the detail error message. When a transaction fails, it just throws a transaction revert error.

Have you tried Remix's debugging tool?

A more hardcore approach is to use trace API, to be more specific the trace_call method. It "simulates" the transaction and returns the execution logs and results(opcode/state changes/successes) for each internal transaction.

It is particularly helpful for debugging complex smart contracts like Arbitrage.

Hope this helps.

2
  • Thanks for this! I tried to debug with Remix based on your suggestion, but my contract wasn’t verified at the time of the transaction. However now it is. Yet still it can’t seem to find my transaction. Is Remix able to look at transactions that occurred prior to a verification ? Commented Feb 16, 2023 at 13:28
  • Update - I ended up running another transaction after contract verification, expecting it to fail. Same result on Remix (it can't find the transaction hash.) Commented Feb 16, 2023 at 16:00
1

After debugging with the help of Wuzhong, I can confidently say that the problem was I didn't have enough money in the account to cover gas. Seems the buffer wasn't there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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