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?