From 2690a5d4c318a59714a0b4d275a2a0c7df72fd90 Mon Sep 17 00:00:00 2001 From: mektigboy <86902415+mektigboy@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:32:47 -0600 Subject: [PATCH 1/2] chore: update seaport address --- .../contracts/src/SudoOpenseaArb.sol | 38 ++--- .../contracts/test/SudoOpenseaArb.t.sol | 136 +++++++++++------- 2 files changed, 109 insertions(+), 65 deletions(-) diff --git a/crates/strategies/opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol b/crates/strategies/opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol index 7a0cf9c..f792fd2 100644 --- a/crates/strategies/opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol +++ b/crates/strategies/opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol @@ -8,45 +8,51 @@ import {IERC721} from "../src/protocols/LSSVMPairFactory/contracts/imports/IERC7 import {Owned} from "solmate/auth/Owned.sol"; contract SudoOpenseaArb is Owned { - error NoProfit(); constructor() Owned(msg.sender) {} - Seaport constant seaport = Seaport(0x00000000000001ad428e4906aE43D8F9852d0dD6); + receive() external payable {} + + fallback() external payable {} + + Seaport constant seaport = + Seaport(0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC); - function executeArb(BasicOrderParameters calldata basicOrder, uint256 paymentValue, address payable sudo_pool) public { - + function executeArb( + BasicOrderParameters calldata basicOrder, + uint256 paymentValue, + address payable sudo_pool + ) public { uint256 initialBalance = address(this).balance; - // buy NFT on opensea + // Buy NFT on OpenSea. seaport.fulfillBasicOrder{value: paymentValue}(basicOrder); - // set approval for sudo pool - IERC721(basicOrder.offerToken).approve(sudo_pool, basicOrder.offerIdentifier); + // Set approval for Sudoswap pool. + IERC721(basicOrder.offerToken).approve( + sudo_pool, + basicOrder.offerIdentifier + ); - // sell into pool + // Sell into Sudoswap pool. uint256[] memory nftIds = new uint256[](1); + nftIds[0] = basicOrder.offerIdentifier; LSSVMPairETH(sudo_pool).swapNFTsForToken( nftIds, - 0, // we don't need to set min output since we revert later on if execution isn't profitable + 0, // No need to set min. output since we might revert later on if execution isn't profitable. payable(address(this)), false, address(0) ); - // revert if we didn't make a profit + // Revert if trade wasn't profitable. if (address(this).balance <= initialBalance) revert NoProfit(); } function withdraw() public onlyOwner { payable(msg.sender).transfer(address(this).balance); } - - fallback() external payable {} - - receive() external payable {} - -} \ No newline at end of file +} diff --git a/crates/strategies/opensea-sudo-arb/contracts/test/SudoOpenseaArb.t.sol b/crates/strategies/opensea-sudo-arb/contracts/test/SudoOpenseaArb.t.sol index 6178c01..ed4d556 100644 --- a/crates/strategies/opensea-sudo-arb/contracts/test/SudoOpenseaArb.t.sol +++ b/crates/strategies/opensea-sudo-arb/contracts/test/SudoOpenseaArb.t.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; + import {Seaport} from "../src/protocols/Seaport/contracts/Seaport.sol"; import {LSSVMPair} from "../src/protocols/LSSVMPairFactory/contracts/LSSVMPair.sol"; import {LSSVMPairETH} from "../src/protocols/LSSVMPairFactory/contracts/LSSVMPairETH.sol"; @@ -16,99 +17,136 @@ import {SudoOpenseaArb} from "../src/SudoOpenseaArb.sol"; import {FixedPointMathLib} from "../lib/solmate/src/utils/FixedPointMathLib.sol"; contract SudoOpenseaArbTest is Test { - - uint256 mainnetFork; - LSSVMPairFactory pairFactory = LSSVMPairFactory(payable(0xb16c1342E617A5B6E4b631EB114483FDB289c0A4)); + LSSVMPairFactory pairFactory = + LSSVMPairFactory(payable(0xb16c1342E617A5B6E4b631EB114483FDB289c0A4)); + ICurve curve = ICurve(0x5B6aC51d9B1CeDE0068a1B26533CAce807f883Ee); + SudoOpenseaArb arb; + uint256 mainnetFork; + function setUp() public { - string memory MAINNET_RPC_URL = vm.envString("ETH_MAINNET_HTTP"); - mainnetFork = vm.createFork(MAINNET_RPC_URL, 16801465); // block where specific order is available + mainnetFork = vm.createFork(vm.envString("ETH_MAINNET_HTTP"), 17573260); // Block where specific order is available. + vm.selectFork(mainnetFork); - arb = new SudoOpenseaArb(); + arb = new SudoOpenseaArb(); } function testArb() public { - vm.selectFork(mainnetFork); (BasicOrderParameters memory order, uint256 price) = getOrderAndPrice(); - // fund arb contract - vm.deal(address(arb), price); - //set up sudo pool + + // Fund arbitrage contract. + vm.deal(address(arb), price); + + // Set up Sudoswap pool. uint128 buyPrice = uint128(order.considerationAmount + 10 ether); + LSSVMPairETH sudoPool = setupSudoPool(order.offerToken, buyPrice); + payable(address(sudoPool)).transfer(buyPrice); - // execute arb + + // Execute arbitrage. + uint256 initialBalance = address(arb).balance; + arb.executeArb(order, price, payable(address(sudoPool))); + uint256 finalBalance = address(arb).balance; + assertTrue(finalBalance > initialBalance); } function testUnprofitableArb() public { - vm.selectFork(mainnetFork); (BasicOrderParameters memory order, uint256 price) = getOrderAndPrice(); - // fund arb contract + + // Fund arbitrage contract. vm.deal(address(arb), price); - //set up sudo pool + + // Set up Sudoswap pool. uint128 buyPrice = uint128(order.considerationAmount - 1); + LSSVMPairETH sudoPool = setupSudoPool(order.offerToken, buyPrice); + payable(address(sudoPool)).transfer(buyPrice); - // execute arb + + // Execute arbitrage. + uint256 initialBalance = address(arb).balance; + vm.expectRevert(SudoOpenseaArb.NoProfit.selector); + arb.executeArb(order, price, payable(address(sudoPool))); + uint256 finalBalance = address(arb).balance; + assertTrue(finalBalance == initialBalance); } - // return a basic order we want to fulfill for our arb - // this is a real order pulled from the opensea api - function getOrderAndPrice() internal pure returns (BasicOrderParameters memory, uint256 price) { - AdditionalRecipient memory recipient1 = AdditionalRecipient(275000000000000, payable(0x0000a26b00c1F0DF003000390027140000fAa719)); - AdditionalRecipient memory recipient2 = AdditionalRecipient(2750000000000000, payable(0xE7DEbDCfd92a2b6f7AB03B4b3c73494CAdCCB371)); - AdditionalRecipient[] memory recipients = new AdditionalRecipient[](2); + /// @notice Get a basic order to fulfill. + /// @dev Real order pulled from the OpenSea API. + function getOrderAndPrice() + internal + pure + returns (BasicOrderParameters memory, uint256) + { + AdditionalRecipient memory recipient1 = AdditionalRecipient( + 2000000000000000000, + payable(0x0000a26b00c1F0DF003000390027140000fAa719) + ); + + AdditionalRecipient[] memory recipients = new AdditionalRecipient[](1); + recipients[0] = recipient1; - recipients[1] = recipient2; BasicOrderParameters memory basicOrder; - - basicOrder.considerationToken = 0x0000000000000000000000000000000000000000; + basicOrder + .considerationToken = 0x0000000000000000000000000000000000000000; basicOrder.considerationIdentifier = 0; - basicOrder.considerationAmount = 51975000000000000; - basicOrder.offerer = payable(0x9278bf6A3222d3f9e778De1267fB26D00ee3ae33); + basicOrder.considerationAmount = 78000000000000000000; + basicOrder.offerer = payable( + 0x175b0777EaFEcA2b5d8C80d3B262be534cFc6229 + ); basicOrder.zone = 0x004C00500000aD104D7DBd00e3ae0A5C00560C00; - basicOrder.offerToken = 0x50ca8e24D80946B9ccF4A15279DfF9eafde7e240; - basicOrder.offerIdentifier = 1222; - basicOrder.offerAmount = 1; + basicOrder.offerToken = 0xD3D9ddd0CF0A5F0BFB8f7fcEAe075DF687eAEBaB; + basicOrder.offerIdentifier = 2145; + basicOrder.offerAmount = 1; basicOrder.basicOrderType = BasicOrderType.ETH_TO_ERC721_FULL_OPEN; - basicOrder.startTime = 1678495668; - basicOrder.endTime = 1681174068; - basicOrder.zoneHash = 0x0000000000000000000000000000000000000000000000000000000000000000; - basicOrder.salt = 24446860302761739304752683030156737591518664810215442929809313052708787659410; - basicOrder.offererConduitKey = 0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000; - basicOrder.fulfillerConduitKey = 0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000; - basicOrder.totalOriginalAdditionalRecipients = 2; - basicOrder.additionalRecipients = recipients; - basicOrder.signature = hex"9e36fd22058e033e8fca619ab589922817c81188273afdca9a82590edc3862a835fa39ef55742209d2079326a6ba02d2ba7b54e5daf2f8d99bcf7fd35285ec8200000165903770b3fc90e3e7b981e525daa72ad3709f2cf65753a37bb8347e5193c90f"; // 0x244 - - return (basicOrder, 55000000000000000); - } + basicOrder.startTime = 1683315963; + basicOrder.endTime = 1698867963; + basicOrder + .zoneHash = 0x0000000000000000000000000000000000000000000000000000000000000000; + basicOrder + .salt = 24446860302761739304752683030156737591518664810215442929812625904300442592992; + basicOrder + .offererConduitKey = 0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000; + basicOrder + .fulfillerConduitKey = 0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000; + basicOrder.totalOriginalAdditionalRecipients = 1; + basicOrder.additionalRecipients = recipients; + basicOrder + .signature = hex"aa86c8d5c3318e667cc48f8f7b33a2cce147ba90b311126d769306ba85ee3646c534d6fde9a644d750c28615422d706f831e4d6897aeb231418079178dd10125"; - // set up pool buying NFTs at price - function setupSudoPool(address nft, uint128 price) internal returns (LSSVMPairETH sudoPool) { + return (basicOrder, 80000000000000000000); + } + /// @notice Set up Sudoswap pool to buy NFTs at a certain price. + /// @param _collection Address of NFT collection. + /// @param _price Price to buy NFTs at. + function setupSudoPool( + address _collection, + uint128 _price + ) internal returns (LSSVMPairETH sudoPool) { sudoPool = pairFactory.createPairETH( - IERC721(nft), - curve, + IERC721(_collection), + curve, payable(address(0)), LSSVMPair.PoolType.TRADE, 0, 0, - price, + _price, new uint256[](0) ); } - -} \ No newline at end of file +} From c3e6bd4fadaed9e52c6168935be95ae9f413a9df Mon Sep 17 00:00:00 2001 From: mektigboy <86902415+mektigboy@users.noreply.github.com> Date: Wed, 5 Jul 2023 09:07:07 -0600 Subject: [PATCH 2/2] fix: receive and fallback --- .../opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/strategies/opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol b/crates/strategies/opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol index f792fd2..8f34291 100644 --- a/crates/strategies/opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol +++ b/crates/strategies/opensea-sudo-arb/contracts/src/SudoOpenseaArb.sol @@ -12,10 +12,6 @@ contract SudoOpenseaArb is Owned { constructor() Owned(msg.sender) {} - receive() external payable {} - - fallback() external payable {} - Seaport constant seaport = Seaport(0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC); @@ -55,4 +51,8 @@ contract SudoOpenseaArb is Owned { function withdraw() public onlyOwner { payable(msg.sender).transfer(address(this).balance); } + + receive() external payable {} + + fallback() external payable {} }