Skip to content
This repository was archived by the owner on May 19, 2025. It is now read-only.
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions skipper-go/contracts/src/Multihop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ contract Multihop is Ownable {
uint256 fee;
}

// --------------------- Errors -------------------- //
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use custom errors to save gas

error ArbitrageNotProfitable();
error WithdrawNativeBalanceFailed();

// ------------------- Functions ------------------- //
/**
* @dev Allows the contract to receive funds
Expand All @@ -35,7 +39,7 @@ contract Multihop is Ownable {
function swapMultihop(
address fromToken,
uint256 fromAmount,
DexHop[] memory route
DexHop[] calldata route
Copy link
Copy Markdown
Author

@jim380 jim380 Apr 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

route is not mutated in the function body so using calldata would save some gas.

) external onlyOwner {
// Get the initial balance of the token
uint256 initialBalance = IERC20(fromToken).balanceOf(address(this));
Expand All @@ -48,24 +52,27 @@ contract Multihop is Ownable {
IERC20(fromToken).transfer(route[0].pairAddress, fromAmount);

// Loop through the route and execute the trades
for (uint256 i = 0; i < route.length; i++) {
DexHop memory hop = route[i];
uint256 length = route.length;
Copy link
Copy Markdown
Author

@jim380 jim380 Apr 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caching array length to avoid repeatedly loading from the state in each iteration.

for (uint256 i = 0; i < length; ) {
DexHop calldata hop = route[i];

address destination = i == route.length - 1
address destination = i == length - 1
? address(this)
: route[i + 1].pairAddress;

fromAmount = swapOnDex(hop, fromAmount, destination);

// Capped by route.length so won't overflow
unchecked {
i += 1;
}
}

// Get the final balance of the token
uint256 finalBalance = IERC20(fromToken).balanceOf(address(this));

// Require that the arbitrage was profitable
require(
finalBalance > initialBalance,
"The arbitrage was not profitable"
);
if (finalBalance <= initialBalance) revert ArbitrageNotProfitable();
}

/**
Expand All @@ -75,7 +82,7 @@ contract Multihop is Ownable {
* @param destination Address to send tokens to
*/
function swapOnDex(
DexHop memory hop,
DexHop calldata hop,
uint256 amount,
address destination
) internal returns (uint256) {
Expand Down Expand Up @@ -115,6 +122,8 @@ contract Multihop is Ownable {
*/
function withdrawNativeBalance() external onlyOwner {
uint256 balance = address(this).balance;
payable(owner()).transfer(balance);

(bool success, ) = owner().call{value: balance}("");
if (!success) revert WithdrawNativeBalanceFailed();
Comment on lines +126 to +127
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.transfer has a hardcoded 2300 gas limit which prevents smart contracts from doing things like sending ether to a gnosis safe due to out of gas. .call instead forwards all gas and is now the recommended way to send ether from a contract. It's also reentrancy safe here since the function is only accessible by the owner.

}
}