From 70599ebe0ecb8bc3fce28ffa6767a2ea2941517f Mon Sep 17 00:00:00 2001 From: JaredBorders Date: Thu, 15 Aug 2024 16:16:13 -0400 Subject: [PATCH 001/129] =?UTF-8?q?=F0=9F=9A=80=20add=20arb=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/utils/parameters/ArbitrumParameters.sol | 18 ++++++++++++++++++ .../parameters/ArbitrumSepoliaParameters.sol | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 script/utils/parameters/ArbitrumParameters.sol create mode 100644 script/utils/parameters/ArbitrumSepoliaParameters.sol diff --git a/script/utils/parameters/ArbitrumParameters.sol b/script/utils/parameters/ArbitrumParameters.sol new file mode 100644 index 0000000..d556689 --- /dev/null +++ b/script/utils/parameters/ArbitrumParameters.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.20; + +contract ArbitrumParameters { + // https://arbiscan + address public constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + + // https://arbiscan + address public constant SPOT_MARKET_PROXY = + 0x18141523403e2595D31b22604AcB8Fc06a4CaA61; + + // https://arbiscan + address public constant USD_PROXY = + 0x09d51516F38980035153a554c26Df3C6f51a23C3; + + // https://arbiscan + uint128 public constant SUSDC_SPOT_MARKET_ID = 1; +} diff --git a/script/utils/parameters/ArbitrumSepoliaParameters.sol b/script/utils/parameters/ArbitrumSepoliaParameters.sol new file mode 100644 index 0000000..27720ab --- /dev/null +++ b/script/utils/parameters/ArbitrumSepoliaParameters.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.20; + +contract ArbitrumSepoliaParameters { + // https://arbiscan + address public constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + + // https://arbiscan + address public constant SPOT_MARKET_PROXY = + 0x18141523403e2595D31b22604AcB8Fc06a4CaA61; + + // https://arbiscan + address public constant USD_PROXY = + 0x09d51516F38980035153a554c26Df3C6f51a23C3; + + // https://arbiscan + uint128 public constant SUSDC_SPOT_MARKET_ID = 1; +} From 75191c5c2f18348e9290c766546787f577ac44e1 Mon Sep 17 00:00:00 2001 From: JaredBorders Date: Thu, 15 Aug 2024 16:16:27 -0400 Subject: [PATCH 002/129] =?UTF-8?q?=F0=9F=9A=80=20add=20arb=20deploy=20scr?= =?UTF-8?q?ipts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index c5c0894..99d464b 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -6,6 +6,9 @@ import {BaseParameters} from "./utils/parameters/BaseParameters.sol"; import {OptimismGoerliParameters} from "./utils/parameters/OptimismGoerliParameters.sol"; import {OptimismParameters} from "./utils/parameters/OptimismParameters.sol"; +import {ArbitrumParameters} from "./utils/parameters/ArbitrumParameters.sol"; +import {ArbitrumSepoliaParameters} from + "./utils/parameters/ArbitrumSepoliaParameters.sol"; import {Script} from "../lib/forge-std/src/Script.sol"; import {Zap} from "../src/Zap.sol"; import {ZapExposed} from "../test/utils/exposed/ZapExposed.sol"; @@ -77,7 +80,6 @@ contract DeployOptimism is Setup, OptimismParameters { /// @dev steps to deploy and verify on Optimism Goerli: /// (1) load the variables in the .env file via `source .env` /// (2) run `forge script script/Deploy.s.sol:DeployOptimismGoerli --rpc-url $OPTIMISM_GOERLI_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv` - contract DeployOptimismGoerli is Setup, OptimismGoerliParameters { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); @@ -90,3 +92,35 @@ contract DeployOptimismGoerli is Setup, OptimismGoerliParameters { vm.stopBroadcast(); } } + +/// @dev steps to deploy and verify on Arbitrum: +/// (1) load the variables in the .env file via `source .env` +/// (2) run `forge script script/Deploy.s.sol:DeployArbitrum --rpc-url $ARBITRUM_GOERLI_RPC_URL --etherscan-api-key $ARBITRUM_GOERLI_RPC_URL --broadcast --verify -vvvv` +contract DeployArbitrum is Setup, ArbitrumParameters { + function run() public { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(privateKey); + + Setup.deploySystem( + USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID + ); + + vm.stopBroadcast(); + } +} + +/// @dev steps to deploy and verify on Arbitrum Sepolia: +/// (1) load the variables in the .env file via `source .env` +/// (2) run `forge script script/Deploy.s.sol:DeployArbitrumSepolia --rpc-url $ARBITRUM_SEPOLIA_RPC_URL --etherscan-api-key $ARBITRUM_SEPOLIA_RPC_URL --broadcast --verify -vvvv` +contract DeployArbitrumSepolia is Setup, ArbitrumSepoliaParameters { + function run() public { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(privateKey); + + Setup.deploySystem( + USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID + ); + + vm.stopBroadcast(); + } +} From acaca8a935954d68dd45bddc85bf8bea645e227f Mon Sep 17 00:00:00 2001 From: JaredBorders Date: Thu, 15 Aug 2024 16:31:11 -0400 Subject: [PATCH 003/129] =?UTF-8?q?=F0=9F=93=9A=20update=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/ISpotMarketProxy.sol | 33 +++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/interfaces/ISpotMarketProxy.sol b/src/interfaces/ISpotMarketProxy.sol index 7db259e..27bf5fa 100644 --- a/src/interfaces/ISpotMarketProxy.sol +++ b/src/interfaces/ISpotMarketProxy.sol @@ -66,13 +66,16 @@ interface ISpotMarketProxy { ATOMIC ORDER MODULE //////////////////////////////////////////////////////////////*/ - /// @notice alias for buyExactIn - /// @param marketId (see buyExactIn) - /// @param usdAmount (see buyExactIn) - /// @param minAmountReceived (see buyExactIn) - /// @param referrer (see buyExactIn) - /// @return synthAmount (see buyExactIn) - /// @return fees (see buyExactIn) + /// @notice Initiates a buy trade returning synth for the specified amountUsd. + /// @dev Transfers the specified amountUsd, collects fees through configured fee collector, returns synth to the trader. + /// @dev Leftover fees not collected get deposited into the market manager to improve market PnL. + /// @dev Uses the buyFeedId configured for the market. + /// @param marketId Id of the market used for the trade. + /// @param usdAmount Amount of snxUSD trader is providing allowance for the trade. + /// @param minAmountReceived Min Amount of synth is expected the trader to receive otherwise the transaction will revert. + /// @param referrer Optional address of the referrer, for fee share + /// @return synthAmount Synth received on the trade based on amount provided by trader. + /// @return fees breakdown of all the fees incurred for the transaction. function buy( uint128 marketId, uint256 usdAmount, @@ -80,13 +83,15 @@ interface ISpotMarketProxy { address referrer ) external returns (uint256 synthAmount, Data memory fees); - /// @notice alias for sellExactIn - /// @param marketId (see sellExactIn) - /// @param synthAmount (see sellExactIn) - /// @param minUsdAmount (see sellExactIn) - /// @param referrer (see sellExactIn) - /// @return usdAmountReceived (see sellExactIn) - /// @return fees (see sellExactIn) + /// @notice Initiates a sell trade returning snxUSD for the specified amount of synth (sellAmount) + /// @dev Transfers the specified synth, collects fees through configured fee collector, returns snxUSD to the trader. + /// @dev Leftover fees not collected get deposited into the market manager to improve market PnL. + /// @param marketId Id of the market used for the trade. + /// @param synthAmount Amount of synth provided by trader for trade into snxUSD. + /// @param minUsdAmount Min Amount of snxUSD trader expects to receive for the trade + /// @param referrer Optional address of the referrer, for fee share + /// @return usdAmountReceived Amount of snxUSD returned to user + /// @return fees breakdown of all the fees incurred for the transaction. function sell( uint128 marketId, uint256 synthAmount, From 3ced1c1690b61ba11443637a7adce753ba8e91b5 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:12:54 -0400 Subject: [PATCH 004/129] =?UTF-8?q?=F0=9F=93=9A=20update=20title?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 5d71660..968a4f7 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -6,8 +6,7 @@ import {ISpotMarketProxy} from "./interfaces/ISpotMarketProxy.sol"; import {ZapErrors} from "./ZapErrors.sol"; import {ZapEvents} from "./ZapEvents.sol"; -/// @title Zap contract for wrapping/unwrapping $USDC into $sUSD -/// via Synthetix v3 Andromeda Spot Market +/// @title Zap contract for zapping collateral in/out of Synthetix v3 /// @author JaredBorders (jaredborders@pm.me) abstract contract Zap is ZapErrors, ZapEvents { /*////////////////////////////////////////////////////////////// From 1a70930c1c3dc02d6aeec43078715f80c7125df6 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:13:32 -0400 Subject: [PATCH 005/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20fmt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- foundry.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/foundry.toml b/foundry.toml index d1d2750..80650c9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,6 +10,11 @@ optimizer_runs = 1_000_000 [fmt] line_length = 80 number_underscore = "thousands" +multiline_func_header = "all" +sort_imports = true +contract_new_lines = true +override_spacing = false +wrap_comments = true [rpc_endpoints] mainnet = "${MAINNET_RPC_URL}" From 88f49fd9b086d2879da9e9a8b62597eca1cb9bd7 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:24:00 -0400 Subject: [PATCH 006/129] =?UTF-8?q?=E2=9C=A8=20fmt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 54 +++++++++--- .../utils/parameters/ArbitrumParameters.sol | 2 + .../parameters/ArbitrumSepoliaParameters.sol | 2 + .../utils/parameters/BaseGoerliParameters.sol | 2 + script/utils/parameters/BaseParameters.sol | 2 + .../parameters/OptimismGoerliParameters.sol | 2 + .../utils/parameters/OptimismParameters.sol | 2 + src/Zap.sol | 14 +++- src/ZapErrors.sol | 2 + src/ZapEvents.sol | 2 + src/interfaces/IERC20.sol | 11 ++- src/interfaces/ISpotMarketProxy.sol | 82 +++++++++++++------ test/Zap.t.sol | 22 +++-- test/utils/Bootstrap.sol | 23 ++++-- test/utils/Constants.sol | 2 + test/utils/errors/SynthetixV3Errors.sol | 5 +- test/utils/exposed/ZapExposed.sol | 6 +- test/utils/mocks/MockSUSD.sol | 8 +- test/utils/mocks/MockSpotMarketProxy.sol | 24 +++++- test/utils/mocks/MockUSDC.sol | 8 +- 20 files changed, 210 insertions(+), 65 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 99d464b..f4d4819 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -1,38 +1,45 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.20; +import {Script} from "../lib/forge-std/src/Script.sol"; +import {Zap} from "../src/Zap.sol"; +import {ZapExposed} from "../test/utils/exposed/ZapExposed.sol"; +import {ArbitrumParameters} from "./utils/parameters/ArbitrumParameters.sol"; +import {ArbitrumSepoliaParameters} from + "./utils/parameters/ArbitrumSepoliaParameters.sol"; import {BaseGoerliParameters} from "./utils/parameters/BaseGoerliParameters.sol"; import {BaseParameters} from "./utils/parameters/BaseParameters.sol"; import {OptimismGoerliParameters} from "./utils/parameters/OptimismGoerliParameters.sol"; import {OptimismParameters} from "./utils/parameters/OptimismParameters.sol"; -import {ArbitrumParameters} from "./utils/parameters/ArbitrumParameters.sol"; -import {ArbitrumSepoliaParameters} from - "./utils/parameters/ArbitrumSepoliaParameters.sol"; -import {Script} from "../lib/forge-std/src/Script.sol"; -import {Zap} from "../src/Zap.sol"; -import {ZapExposed} from "../test/utils/exposed/ZapExposed.sol"; /// @title Zap deployment script /// @notice ZapExposed is deployed (not Zap) and /// ZapExposed is unsafe and not meant for production /// @author JaredBorders (jaredborders@pm.me) contract Setup is Script { + function deploySystem( address _usdc, address _susd, address _spotMarketProxy, uint128 _sUSDCId - ) public returns (address zapAddress) { + ) + public + returns (address zapAddress) + { zapAddress = address(new ZapExposed(_usdc, _susd, _spotMarketProxy, _sUSDCId)); } + } /// @dev steps to deploy and verify on Base: /// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployBase --rpc-url $BASE_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv` +/// (2) run `forge script script/Deploy.s.sol:DeployBase --rpc-url $BASE_RPC_URL +/// --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv` contract DeployBase is Setup, BaseParameters { + function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); @@ -43,12 +50,16 @@ contract DeployBase is Setup, BaseParameters { vm.stopBroadcast(); } + } /// @dev steps to deploy and verify on Base Goerli: /// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployBaseGoerli --rpc-url $BASE_GOERLI_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv` +/// (2) run `forge script script/Deploy.s.sol:DeployBaseGoerli --rpc-url +/// $BASE_GOERLI_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast +/// --verify -vvvv` contract DeployBaseGoerli is Setup, BaseGoerliParameters { + function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); @@ -59,12 +70,16 @@ contract DeployBaseGoerli is Setup, BaseGoerliParameters { vm.stopBroadcast(); } + } /// @dev steps to deploy and verify on Optimism: /// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployOptimism --rpc-url $OPTIMISM_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv` +/// (2) run `forge script script/Deploy.s.sol:DeployOptimism --rpc-url +/// $OPTIMISM_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY +/// --broadcast --verify -vvvv` contract DeployOptimism is Setup, OptimismParameters { + function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); @@ -75,12 +90,16 @@ contract DeployOptimism is Setup, OptimismParameters { vm.stopBroadcast(); } + } /// @dev steps to deploy and verify on Optimism Goerli: /// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployOptimismGoerli --rpc-url $OPTIMISM_GOERLI_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv` +/// (2) run `forge script script/Deploy.s.sol:DeployOptimismGoerli --rpc-url +/// $OPTIMISM_GOERLI_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY +/// --broadcast --verify -vvvv` contract DeployOptimismGoerli is Setup, OptimismGoerliParameters { + function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); @@ -91,12 +110,16 @@ contract DeployOptimismGoerli is Setup, OptimismGoerliParameters { vm.stopBroadcast(); } + } /// @dev steps to deploy and verify on Arbitrum: /// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployArbitrum --rpc-url $ARBITRUM_GOERLI_RPC_URL --etherscan-api-key $ARBITRUM_GOERLI_RPC_URL --broadcast --verify -vvvv` +/// (2) run `forge script script/Deploy.s.sol:DeployArbitrum --rpc-url +/// $ARBITRUM_GOERLI_RPC_URL --etherscan-api-key $ARBITRUM_GOERLI_RPC_URL +/// --broadcast --verify -vvvv` contract DeployArbitrum is Setup, ArbitrumParameters { + function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); @@ -107,12 +130,16 @@ contract DeployArbitrum is Setup, ArbitrumParameters { vm.stopBroadcast(); } + } /// @dev steps to deploy and verify on Arbitrum Sepolia: /// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployArbitrumSepolia --rpc-url $ARBITRUM_SEPOLIA_RPC_URL --etherscan-api-key $ARBITRUM_SEPOLIA_RPC_URL --broadcast --verify -vvvv` +/// (2) run `forge script script/Deploy.s.sol:DeployArbitrumSepolia --rpc-url +/// $ARBITRUM_SEPOLIA_RPC_URL --etherscan-api-key $ARBITRUM_SEPOLIA_RPC_URL +/// --broadcast --verify -vvvv` contract DeployArbitrumSepolia is Setup, ArbitrumSepoliaParameters { + function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); @@ -123,4 +150,5 @@ contract DeployArbitrumSepolia is Setup, ArbitrumSepoliaParameters { vm.stopBroadcast(); } + } diff --git a/script/utils/parameters/ArbitrumParameters.sol b/script/utils/parameters/ArbitrumParameters.sol index d556689..82ff863 100644 --- a/script/utils/parameters/ArbitrumParameters.sol +++ b/script/utils/parameters/ArbitrumParameters.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; contract ArbitrumParameters { + // https://arbiscan address public constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; @@ -15,4 +16,5 @@ contract ArbitrumParameters { // https://arbiscan uint128 public constant SUSDC_SPOT_MARKET_ID = 1; + } diff --git a/script/utils/parameters/ArbitrumSepoliaParameters.sol b/script/utils/parameters/ArbitrumSepoliaParameters.sol index 27720ab..85547a9 100644 --- a/script/utils/parameters/ArbitrumSepoliaParameters.sol +++ b/script/utils/parameters/ArbitrumSepoliaParameters.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; contract ArbitrumSepoliaParameters { + // https://arbiscan address public constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; @@ -15,4 +16,5 @@ contract ArbitrumSepoliaParameters { // https://arbiscan uint128 public constant SUSDC_SPOT_MARKET_ID = 1; + } diff --git a/script/utils/parameters/BaseGoerliParameters.sol b/script/utils/parameters/BaseGoerliParameters.sol index 18a6085..f86ea2c 100644 --- a/script/utils/parameters/BaseGoerliParameters.sol +++ b/script/utils/parameters/BaseGoerliParameters.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; contract BaseGoerliParameters { + // https://goerli.basescan.org/token/0xf175520c52418dfe19c8098071a252da48cd1c19 address public constant USDC = 0xF175520C52418dfE19C8098071a252da48Cd1C19; @@ -15,4 +16,5 @@ contract BaseGoerliParameters { // https://usecannon.com/packages/synthetix-spot-market/3.3.5/84531-andromeda uint128 public constant SUSDC_SPOT_MARKET_ID = 1; + } diff --git a/script/utils/parameters/BaseParameters.sol b/script/utils/parameters/BaseParameters.sol index cd094eb..0728548 100644 --- a/script/utils/parameters/BaseParameters.sol +++ b/script/utils/parameters/BaseParameters.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; contract BaseParameters { + // https://basescan.org/token/0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 address public constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; @@ -15,4 +16,5 @@ contract BaseParameters { // https://usecannon.com/packages/synthetix-spot-market/3.3.5/84531-andromeda uint128 public constant SUSDC_SPOT_MARKET_ID = 1; + } diff --git a/script/utils/parameters/OptimismGoerliParameters.sol b/script/utils/parameters/OptimismGoerliParameters.sol index 60b7170..a4afe2d 100644 --- a/script/utils/parameters/OptimismGoerliParameters.sol +++ b/script/utils/parameters/OptimismGoerliParameters.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; contract OptimismGoerliParameters { + address public constant USDC = address(0); address public constant SPOT_MARKET_PROXY = address(0); @@ -9,4 +10,5 @@ contract OptimismGoerliParameters { address public constant USD_PROXY = address(0); uint128 public constant SUSDC_SPOT_MARKET_ID = type(uint128).max; + } diff --git a/script/utils/parameters/OptimismParameters.sol b/script/utils/parameters/OptimismParameters.sol index 9302fc3..ce27c84 100644 --- a/script/utils/parameters/OptimismParameters.sol +++ b/script/utils/parameters/OptimismParameters.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; contract OptimismParameters { + address public constant USDC = address(0); address public constant SPOT_MARKET_PROXY = address(0); @@ -9,4 +10,5 @@ contract OptimismParameters { address public constant USD_PROXY = address(0); uint128 public constant SUSDC_SPOT_MARKET_ID = type(uint128).max; + } diff --git a/src/Zap.sol b/src/Zap.sol index 968a4f7..6e09bdc 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.20; -import {IERC20} from "./interfaces/IERC20.sol"; -import {ISpotMarketProxy} from "./interfaces/ISpotMarketProxy.sol"; import {ZapErrors} from "./ZapErrors.sol"; import {ZapEvents} from "./ZapEvents.sol"; +import {IERC20} from "./interfaces/IERC20.sol"; +import {ISpotMarketProxy} from "./interfaces/ISpotMarketProxy.sol"; /// @title Zap contract for zapping collateral in/out of Synthetix v3 /// @author JaredBorders (jaredborders@pm.me) abstract contract Zap is ZapErrors, ZapEvents { + /*////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ @@ -94,7 +95,9 @@ abstract contract Zap is ZapErrors, ZapEvents { /// @dev assumes zero fees when /// wrapping/unwrapping/selling/buying /// @param _amount is the amount of $USDC to wrap - function _zapIn(uint256 _amount) + function _zapIn( + uint256 _amount + ) internal virtual returns (uint256 adjustedAmount) @@ -169,7 +172,9 @@ abstract contract Zap is ZapErrors, ZapEvents { /// when n is a number less than 1e12; n $sUSDC is lost /// @param _amount is the amount of $sUSD to sell /// for $sUSDC and then unwrap - function _zapOut(uint256 _amount) + function _zapOut( + uint256 _amount + ) internal virtual returns (uint256 adjustedAmount) @@ -236,4 +241,5 @@ abstract contract Zap is ZapErrors, ZapEvents { emit ZappedOut({amountBurned: _amount, amountUnwrapped: adjustedAmount}); } + } diff --git a/src/ZapErrors.sol b/src/ZapErrors.sol index b4641dd..ad19fdc 100644 --- a/src/ZapErrors.sol +++ b/src/ZapErrors.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.20; /// @title Zap contract errors /// @author JaredBorders (jaredborders@pm.me) contract ZapErrors { + /// @notice thrown when $USDC address is zero /// @dev only can be thrown in during Zap deployment error USDCZeroAddress(); @@ -51,4 +52,5 @@ contract ZapErrors { /// then it is possible that the amount of /// $sUSDC to unwrap is less than 1 $USDC error InsufficientAmount(uint256 amount); + } diff --git a/src/ZapEvents.sol b/src/ZapEvents.sol index 3fb73d9..8923e52 100644 --- a/src/ZapEvents.sol +++ b/src/ZapEvents.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.20; /// @title Zap contract events /// @author JaredBorders (jaredborders@pm.me) contract ZapEvents { + /// @notice emitted after successful $USDC -> $sUSD zap /// @param amountWrapped amount of $USDC wrapped /// @param amountMinted amount of $sUSD minted @@ -13,4 +14,5 @@ contract ZapEvents { /// @param amountBurned amount of $sUSD burned /// @param amountUnwrapped amount of $USDC unwrapped event ZappedOut(uint256 amountBurned, uint256 amountUnwrapped); + } diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 93c9eb4..85f896d 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.20; /// @title Reduced Interface of the ERC20 standard as defined in the EIP /// @author OpenZeppelin interface IERC20 { + /// @dev Returns the number of decimals used to get its user representation. /// For example, if `decimals` equals `2`, a balance of `505` tokens should /// be displayed to a user as `5.05` (`505 / 10 ** 2`). @@ -19,7 +20,8 @@ interface IERC20 { /// Emits a {Transfer} event function transfer(address to, uint256 amount) external returns (bool); - /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens + /// @dev Sets `amount` as the allowance of `spender` over the caller's + /// tokens /// @param spender The address of the account to grant the allowance /// @param amount The amount of tokens to allow /// @return a boolean value indicating whether the operation succeeded @@ -34,7 +36,12 @@ interface IERC20 { /// @param amount The amount of tokens to transfer /// @return a boolean value indicating whether the operation succeeded. /// Emits a {Transfer} event - function transferFrom(address from, address to, uint256 amount) + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); + } diff --git a/src/interfaces/ISpotMarketProxy.sol b/src/interfaces/ISpotMarketProxy.sol index 27bf5fa..1a6c3fb 100644 --- a/src/interfaces/ISpotMarketProxy.sol +++ b/src/interfaces/ISpotMarketProxy.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.20; /// @notice Responsible for interacting with Synthetix v3 spot markets /// @author Synthetix interface ISpotMarketProxy { + /*////////////////////////////////////////////////////////////// MARKET INTERFACE //////////////////////////////////////////////////////////////*/ @@ -20,7 +21,9 @@ interface ISpotMarketProxy { /// @dev Uses associated systems module to retrieve the token address. /// @param marketId id of the market /// @return synthAddress address of the proxy for the synth - function getSynth(uint128 marketId) + function getSynth( + uint128 marketId + ) external view returns (address synthAddress); @@ -36,59 +39,85 @@ interface ISpotMarketProxy { int256 wrapperFees; } - /// @notice Wraps the specified amount and returns similar value of synth minus the fees. - /// @dev Fees are collected from the user by way of the contract returning less synth than specified amount of collateral. + /// @notice Wraps the specified amount and returns similar value of synth + /// minus the fees. + /// @dev Fees are collected from the user by way of the contract returning + /// less synth than specified amount of collateral. /// @param marketId Id of the market used for the trade. - /// @param wrapAmount Amount of collateral to wrap. This amount gets deposited into the market collateral manager. - /// @param minAmountReceived The minimum amount of synths the trader is expected to receive, otherwise the transaction will revert. + /// @param wrapAmount Amount of collateral to wrap. This amount gets + /// deposited into the market collateral manager. + /// @param minAmountReceived The minimum amount of synths the trader is + /// expected to receive, otherwise the transaction will revert. /// @return amountToMint Amount of synth returned to user. - /// @return fees breakdown of all fees. in this case, only wrapper fees are returned. + /// @return fees breakdown of all fees. in this case, only wrapper fees are + /// returned. function wrap( uint128 marketId, uint256 wrapAmount, uint256 minAmountReceived - ) external returns (uint256 amountToMint, Data memory fees); + ) + external + returns (uint256 amountToMint, Data memory fees); - /// @notice Unwraps the synth and returns similar value of collateral minus the fees. - /// @dev Transfers the specified synth, collects fees through configured fee collector, returns collateral minus fees to trader. + /// @notice Unwraps the synth and returns similar value of collateral minus + /// the fees. + /// @dev Transfers the specified synth, collects fees through configured fee + /// collector, returns collateral minus fees to trader. /// @param marketId Id of the market used for the trade. /// @param unwrapAmount Amount of synth trader is unwrapping. - /// @param minAmountReceived The minimum amount of collateral the trader is expected to receive, otherwise the transaction will revert. + /// @param minAmountReceived The minimum amount of collateral the trader is + /// expected to receive, otherwise the transaction will revert. /// @return returnCollateralAmount Amount of collateral returned. - /// @return fees breakdown of all fees. in this case, only wrapper fees are returned. + /// @return fees breakdown of all fees. in this case, only wrapper fees are + /// returned. function unwrap( uint128 marketId, uint256 unwrapAmount, uint256 minAmountReceived - ) external returns (uint256 returnCollateralAmount, Data memory fees); + ) + external + returns (uint256 returnCollateralAmount, Data memory fees); /*////////////////////////////////////////////////////////////// ATOMIC ORDER MODULE //////////////////////////////////////////////////////////////*/ - /// @notice Initiates a buy trade returning synth for the specified amountUsd. - /// @dev Transfers the specified amountUsd, collects fees through configured fee collector, returns synth to the trader. - /// @dev Leftover fees not collected get deposited into the market manager to improve market PnL. + /// @notice Initiates a buy trade returning synth for the specified + /// amountUsd. + /// @dev Transfers the specified amountUsd, collects fees through configured + /// fee collector, returns synth to the trader. + /// @dev Leftover fees not collected get deposited into the market manager + /// to improve market PnL. /// @dev Uses the buyFeedId configured for the market. /// @param marketId Id of the market used for the trade. - /// @param usdAmount Amount of snxUSD trader is providing allowance for the trade. - /// @param minAmountReceived Min Amount of synth is expected the trader to receive otherwise the transaction will revert. + /// @param usdAmount Amount of snxUSD trader is providing allowance for the + /// trade. + /// @param minAmountReceived Min Amount of synth is expected the trader to + /// receive otherwise the transaction will revert. /// @param referrer Optional address of the referrer, for fee share - /// @return synthAmount Synth received on the trade based on amount provided by trader. + /// @return synthAmount Synth received on the trade based on amount provided + /// by trader. /// @return fees breakdown of all the fees incurred for the transaction. function buy( uint128 marketId, uint256 usdAmount, uint256 minAmountReceived, address referrer - ) external returns (uint256 synthAmount, Data memory fees); + ) + external + returns (uint256 synthAmount, Data memory fees); - /// @notice Initiates a sell trade returning snxUSD for the specified amount of synth (sellAmount) - /// @dev Transfers the specified synth, collects fees through configured fee collector, returns snxUSD to the trader. - /// @dev Leftover fees not collected get deposited into the market manager to improve market PnL. + /// @notice Initiates a sell trade returning snxUSD for the specified amount + /// of synth (sellAmount) + /// @dev Transfers the specified synth, collects fees through configured fee + /// collector, returns snxUSD to the trader. + /// @dev Leftover fees not collected get deposited into the market manager + /// to improve market PnL. /// @param marketId Id of the market used for the trade. - /// @param synthAmount Amount of synth provided by trader for trade into snxUSD. - /// @param minUsdAmount Min Amount of snxUSD trader expects to receive for the trade + /// @param synthAmount Amount of synth provided by trader for trade into + /// snxUSD. + /// @param minUsdAmount Min Amount of snxUSD trader expects to receive for + /// the trade /// @param referrer Optional address of the referrer, for fee share /// @return usdAmountReceived Amount of snxUSD returned to user /// @return fees breakdown of all the fees incurred for the transaction. @@ -97,5 +126,8 @@ interface ISpotMarketProxy { uint256 synthAmount, uint256 minUsdAmount, address referrer - ) external returns (uint256 usdAmountReceived, Data memory fees); + ) + external + returns (uint256 usdAmountReceived, Data memory fees); + } diff --git a/test/Zap.t.sol b/test/Zap.t.sol index e10c34c..c1a6a90 100644 --- a/test/Zap.t.sol +++ b/test/Zap.t.sol @@ -1,15 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.20; +import {ZapErrors} from "../src/ZapErrors.sol"; +import {IERC20} from "../src/interfaces/IERC20.sol"; +import {ISpotMarketProxy} from "../src/interfaces/ISpotMarketProxy.sol"; import { Bootstrap, + MockSUSD, MockSpotMarketProxy, - MockUSDC, - MockSUSD + MockUSDC } from "./utils/Bootstrap.sol"; -import {IERC20} from "../src/interfaces/IERC20.sol"; -import {ISpotMarketProxy} from "../src/interfaces/ISpotMarketProxy.sol"; -import {ZapErrors} from "../src/ZapErrors.sol"; + import {ZapExposed} from "./utils/exposed/ZapExposed.sol"; /** @@ -33,9 +34,11 @@ import {ZapExposed} from "./utils/exposed/ZapExposed.sol"; * IF $USDC has 6 decimals * AND $sUSD and $sUSDC have 18 decimals * THEN 1e12 $sUSD/$sUSDC = 1 $USDC - * AND in the context of this system, the DECIMAL_FACTOR would be: 10^(18-6) == 1e12 + * AND in the context of this system, the DECIMAL_FACTOR would be: 10^(18-6) == + * 1e12 */ contract ZapTest is Bootstrap { + IERC20 internal SUSD; IERC20 internal USDC; IERC20 internal SUSDC; @@ -72,9 +75,11 @@ contract ZapTest is Bootstrap { DECIMAL_FACTOR = zap.expose_DECIMALS_FACTOR(); } + } contract Deployment is ZapTest { + function test_zap_address() public view { assert(address(zap) != address(0)); } @@ -147,9 +152,11 @@ contract Deployment is ZapTest { function test_sUSDC_address() public view { assert(zap.expose_SUSDC() != address(0)); } + } contract ZapIn is ZapTest { + function test_zap_in() public { vm.startPrank(ACTOR); @@ -272,9 +279,11 @@ contract ZapIn is ZapTest { vm.stopPrank(); } + } contract ZapOut is ZapTest { + function test_zap_out() public { vm.startPrank(ACTOR); @@ -397,4 +406,5 @@ contract ZapOut is ZapTest { vm.stopPrank(); } + } diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol index 58cb298..49269c3 100644 --- a/test/utils/Bootstrap.sol +++ b/test/utils/Bootstrap.sol @@ -1,25 +1,27 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.20; +import {Test} from "../../lib/forge-std/src/Test.sol"; import {console2} from "../../lib/forge-std/src/console2.sol"; import { - Zap, BaseGoerliParameters, BaseParameters, - Setup + Setup, + Zap } from "../../script/Deploy.s.sol"; -import {ZapEvents} from "./../../src/ZapEvents.sol"; -import {Constants} from "./Constants.sol"; +import {ZapExposed} from "../utils/exposed/ZapExposed.sol"; +import {MockSUSD} from "../utils/mocks/MockSUSD.sol"; import {MockSpotMarketProxy} from "../utils/mocks/MockSpotMarketProxy.sol"; import {MockUSDC} from "../utils/mocks/MockUSDC.sol"; -import {MockSUSD} from "../utils/mocks/MockSUSD.sol"; +import {ZapEvents} from "./../../src/ZapEvents.sol"; +import {Constants} from "./Constants.sol"; + import {SynthetixV3Errors} from "./errors/SynthetixV3Errors.sol"; -import {Test} from "../../lib/forge-std/src/Test.sol"; -import {ZapExposed} from "../utils/exposed/ZapExposed.sol"; /// @title Bootstrap contract for setting up test environment /// @author JaredBorders (jaredborders@pm.me) contract Bootstrap is Test, ZapEvents, SynthetixV3Errors, Constants { + using console2 for *; ZapExposed public zap; @@ -44,9 +46,11 @@ contract Bootstrap is Test, ZapEvents, SynthetixV3Errors, Constants { zap = ZapExposed(zapAddress); } + } contract BootstrapLocal is Setup, Constants { + function init() public returns (address zapAddress) { zapAddress = Setup.deploySystem( address(new MockUSDC()), @@ -55,20 +59,25 @@ contract BootstrapLocal is Setup, Constants { type(uint128).max ); } + } contract BootstrapBase is Setup, BaseParameters { + function init() public returns (address zapAddress) { zapAddress = Setup.deploySystem( USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID ); } + } contract BootstrapBaseGoerli is Setup, BaseGoerliParameters { + function init() public returns (address zapAddress) { zapAddress = Setup.deploySystem( USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID ); } + } diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index 6f97d23..7d7a056 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.20; /// @title Contract for defining constants used in testing /// @author JaredBorders (jaredborders@pm.me) contract Constants { + uint256 public constant BASE_BLOCK_NUMBER = 8_163_300; address public constant ACTOR = address(0xAC7AC7AC7); @@ -11,4 +12,5 @@ contract Constants { uint256 public constant INITIAL_MINT = type(uint256).max / 10; uint256 public constant AMOUNT = 1000; + } diff --git a/test/utils/errors/SynthetixV3Errors.sol b/test/utils/errors/SynthetixV3Errors.sol index 3410803..865b8dc 100644 --- a/test/utils/errors/SynthetixV3Errors.sol +++ b/test/utils/errors/SynthetixV3Errors.sol @@ -2,11 +2,14 @@ pragma solidity 0.8.20; /// @title Cosolidated Errors from Synthetix v3 contracts -/// @notice This contract consolidates all necessary errors from Synthetix v3 contracts +/// @notice This contract consolidates all necessary errors from Synthetix v3 +/// contracts /// and is used for testing purposes /// @author JaredBorders (jaredborders@pm.me) contract SynthetixV3Errors { + error WrapperExceedsMaxAmount( uint256 maxWrappableAmount, uint256 currentSupply, uint256 amountToWrap ); + } diff --git a/test/utils/exposed/ZapExposed.sol b/test/utils/exposed/ZapExposed.sol index aa77634..31473de 100644 --- a/test/utils/exposed/ZapExposed.sol +++ b/test/utils/exposed/ZapExposed.sol @@ -8,6 +8,7 @@ import {Zap} from "../../../src/Zap.sol"; /// *not* safe and *not* meant for mainnet /// @author JaredBorders (jaredborders@pm.me) contract ZapExposed is Zap { + function expose_HASHED_SUSDC_NAME() public pure returns (bytes32) { return _HASHED_SUSDC_NAME; } @@ -41,7 +42,9 @@ contract ZapExposed is Zap { address _susd, address _spotMarketProxy, uint128 _sUSDCId - ) Zap(_usdc, _susd, _spotMarketProxy, _sUSDCId) {} + ) + Zap(_usdc, _susd, _spotMarketProxy, _sUSDCId) + {} function expose_zapIn(uint256 _amount) public { _zapIn(_amount); @@ -50,4 +53,5 @@ contract ZapExposed is Zap { function expose_zapOut(uint256 _amount) public { _zapOut(_amount); } + } diff --git a/test/utils/mocks/MockSUSD.sol b/test/utils/mocks/MockSUSD.sol index 7943b72..c4b573c 100644 --- a/test/utils/mocks/MockSUSD.sol +++ b/test/utils/mocks/MockSUSD.sol @@ -6,6 +6,7 @@ import {IERC20} from "./../../../src/interfaces/IERC20.sol"; /// @title MockSUSD contract for testing /// @author JaredBorders (jaredborders@pm.me) contract MockSUSD is IERC20 { + function decimals() external pure override returns (uint8) { return 18; } @@ -22,7 +23,11 @@ contract MockSUSD is IERC20 { return true; } - function transferFrom(address, address, uint256) + function transferFrom( + address, + address, + uint256 + ) external pure override @@ -30,4 +35,5 @@ contract MockSUSD is IERC20 { { return true; } + } diff --git a/test/utils/mocks/MockSpotMarketProxy.sol b/test/utils/mocks/MockSpotMarketProxy.sol index c276773..62e025a 100644 --- a/test/utils/mocks/MockSpotMarketProxy.sol +++ b/test/utils/mocks/MockSpotMarketProxy.sol @@ -6,7 +6,10 @@ import {ISpotMarketProxy} from "./../../../src/interfaces/ISpotMarketProxy.sol"; /// @title MockSpotMarketProxy contract for testing /// @author JaredBorders (jaredborders@pm.me) contract MockSpotMarketProxy is ISpotMarketProxy { - function name(uint128 /* marketId */ ) + + function name( + uint128 /* marketId */ + ) external pure override @@ -15,7 +18,9 @@ contract MockSpotMarketProxy is ISpotMarketProxy { return "MockName"; } - function getSynth(uint128 /* marketId */ ) + function getSynth( + uint128 /* marketId */ + ) external pure override @@ -28,7 +33,12 @@ contract MockSpotMarketProxy is ISpotMarketProxy { uint128, /* marketId */ uint256 wrapAmount, uint256 /* minAmountReceived */ - ) external pure override returns (uint256 amountToMint, Data memory fees) { + ) + external + pure + override + returns (uint256 amountToMint, Data memory fees) + { return (wrapAmount, Data(0, 0, 0, 0)); } @@ -50,7 +60,12 @@ contract MockSpotMarketProxy is ISpotMarketProxy { uint256 usdAmount, uint256, /* minAmountReceived */ address /* referrer */ - ) external pure override returns (uint256 synthAmount, Data memory fees) { + ) + external + pure + override + returns (uint256 synthAmount, Data memory fees) + { return (usdAmount, Data(0, 0, 0, 0)); } @@ -67,4 +82,5 @@ contract MockSpotMarketProxy is ISpotMarketProxy { { return (synthAmount, Data(0, 0, 0, 0)); } + } diff --git a/test/utils/mocks/MockUSDC.sol b/test/utils/mocks/MockUSDC.sol index ae5a1b6..c3f220a 100644 --- a/test/utils/mocks/MockUSDC.sol +++ b/test/utils/mocks/MockUSDC.sol @@ -6,6 +6,7 @@ import {IERC20} from "./../../../src/interfaces/IERC20.sol"; /// @title MockSUSD contract for testing /// @author JaredBorders (jaredborders@pm.me) contract MockUSDC is IERC20 { + function decimals() external pure override returns (uint8) { return 6; } @@ -22,7 +23,11 @@ contract MockUSDC is IERC20 { return true; } - function transferFrom(address, address, uint256) + function transferFrom( + address, + address, + uint256 + ) external pure override @@ -30,4 +35,5 @@ contract MockUSDC is IERC20 { { return true; } + } From f5be42fe2f1302f0e9ec2b8697b9777430e46f18 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:24:03 -0400 Subject: [PATCH 007/129] =?UTF-8?q?=F0=9F=91=B7=E2=80=8D=E2=99=82=EF=B8=8F?= =?UTF-8?q?=20update=20zap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 289 +++++++++++++--------------------------------------- 1 file changed, 68 insertions(+), 221 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 6e09bdc..50e4c07 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; import {ZapErrors} from "./ZapErrors.sol"; import {ZapEvents} from "./ZapEvents.sol"; @@ -8,238 +8,85 @@ import {ISpotMarketProxy} from "./interfaces/ISpotMarketProxy.sol"; /// @title Zap contract for zapping collateral in/out of Synthetix v3 /// @author JaredBorders (jaredborders@pm.me) -abstract contract Zap is ZapErrors, ZapEvents { - - /*////////////////////////////////////////////////////////////// - CONSTANTS - //////////////////////////////////////////////////////////////*/ - - /// @notice keccak256 hash of expected name of $sUSDC synth - /// @dev pre-computed to save gas during deployment: - /// keccak256(abi.encodePacked("Synthetic USD Coin Spot Market")) - bytes32 internal constant _HASHED_SUSDC_NAME = - 0xdb59c31a60f6ecfcb2e666ed077a3791b5c753b5a5e8dc5120f29367b94bbb22; - - /*////////////////////////////////////////////////////////////// - IMMUTABLES - //////////////////////////////////////////////////////////////*/ - - /// @notice $USDC token contract address - IERC20 internal immutable _USDC; - - /// @notice $sUSD token/synth contract address - IERC20 internal immutable _SUSD; - - /// @notice $sUSDC token/synth contract address - IERC20 internal immutable _SUSDC; - - /// @notice Synthetix v3 Spot Market ID for $sUSDC - uint128 internal immutable _SUSDC_SPOT_MARKET_ID; - - /// @notice Synthetix v3 Spot Market Proxy contract address - ISpotMarketProxy internal immutable _SPOT_MARKET_PROXY; - - /// @notice used to adjust $USDC decimals - uint256 internal immutable _DECIMALS_FACTOR; - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /// @notice Zap constructor - /// @dev will revert if any of the addresses are zero - /// @dev will revert if the Synthetix v3 Spot Market ID for - /// $sUSDC is incorrect - /// @param _usdc $USDC token contract address - /// @param _susd $sUSD token contract address - /// @param _spotMarketProxy Synthetix v3 Spot Market Proxy - /// contract address - /// @param _sUSDCId Synthetix v3 Spot Market ID for $sUSDC - constructor( - address _usdc, - address _susd, - address _spotMarketProxy, - uint128 _sUSDCId - ) { - if (_usdc == address(0)) revert USDCZeroAddress(); - if (_susd == address(0)) revert SUSDZeroAddress(); - if (_spotMarketProxy == address(0)) revert SpotMarketZeroAddress(); - - _USDC = IERC20(_usdc); - _SUSD = IERC20(_susd); - _SPOT_MARKET_PROXY = ISpotMarketProxy(_spotMarketProxy); - - _DECIMALS_FACTOR = 10 ** (18 - IERC20(_usdc).decimals()); - - if ( - keccak256(abi.encodePacked(_SPOT_MARKET_PROXY.name(_sUSDCId))) - != _HASHED_SUSDC_NAME - ) revert InvalidIdSUSDC(_sUSDCId); - - // id of $sUSDC is verified to be correct via the above - // name comparison check - _SUSDC_SPOT_MARKET_ID = _sUSDCId; - _SUSDC = IERC20(_SPOT_MARKET_PROXY.getSynth(_sUSDCId)); +contract Zap is ZapErrors, ZapEvents { + + uint128 internal constant USD_SPOT_MARKET_ID = 0; + + enum Direction { + In, + Out + } + + struct Tolerance { + uint256 tolerableWrapAmount; + uint256 tolerableSwapAmount; + } + + struct ZapData { + ISpotMarketProxy spotMarket; + IERC20 collateral; + uint128 marketId; + uint256 amount; + Tolerance tolerance; + Direction direction; + address receiver; + address referrer; + } + + function zap(ZapData calldata _data) external { + _data.direction == Direction.In ? _zapIn(_data) : _zapOut(_data); } - /*////////////////////////////////////////////////////////////// - ZAP IN - //////////////////////////////////////////////////////////////*/ - - /// @notice wrap $USDC into $sUSD - /// @dev call will result in $sUSD minted to this contract - /// @dev override this function to include additional logic - /// @dev wrapping $USDC requires sufficient Zap - /// contract $USDC allowance and results in a - /// 1:1 ratio in terms of value - /// @dev assumes zero fees when - /// wrapping/unwrapping/selling/buying - /// @param _amount is the amount of $USDC to wrap - function _zapIn( - uint256 _amount - ) - internal - virtual - returns (uint256 adjustedAmount) - { - // transfer $USDC to the Zap contract - if (!_USDC.transferFrom(msg.sender, address(this), _amount)) { - revert TransferFailed( - address(_USDC), msg.sender, address(this), _amount - ); - } - - // allocate $USDC allowance to the Spot Market Proxy - if (!_USDC.approve(address(_SPOT_MARKET_PROXY), _amount)) { - revert ApprovalFailed( - address(_USDC), - address(this), - address(_SPOT_MARKET_PROXY), - _amount - ); - } - - /// @notice $USDC may use non-standard decimals - /// @dev adjustedAmount is the amount of $sUSDC - /// expected to receive from wrapping - /// @dev Synthetix synths use 18 decimals - /// @custom:example if $USDC has 6 decimals, - /// and $sUSD and $sUSDC have 18 decimals, - /// then, 1e12 $sUSD/$sUSDC = 1 $USDC - adjustedAmount = _amount * _DECIMALS_FACTOR; - - /// @notice wrap $USDC into $sUSDC - /// @dev call will result in $sUSDC minted/transferred - /// to the Zap contract - _SPOT_MARKET_PROXY.wrap({ - marketId: _SUSDC_SPOT_MARKET_ID, - wrapAmount: _amount, - minAmountReceived: adjustedAmount + function _zapIn(ZapData calldata _data) private { + uint256 amount = _data.amount; + + _data.collateral.transferFrom(msg.sender, address(this), amount); + _data.collateral.approve(address(_data.spotMarket), amount); + + (amount,) = _data.spotMarket.wrap({ + marketId: _data.marketId, + wrapAmount: amount, + minAmountReceived: _data.tolerance.tolerableWrapAmount }); - // allocate $sUSDC allowance to the Spot Market Proxy - if (!_SUSDC.approve(address(_SPOT_MARKET_PROXY), adjustedAmount)) { - revert ApprovalFailed( - address(_SUSDC), - address(this), - address(_SPOT_MARKET_PROXY), - adjustedAmount - ); - } - - /// @notice sell $sUSDC for $sUSD - /// @dev call will result in $sUSD minted/transferred - /// to the Zap contract - _SPOT_MARKET_PROXY.sell({ - marketId: _SUSDC_SPOT_MARKET_ID, - synthAmount: adjustedAmount, - minUsdAmount: adjustedAmount, - referrer: address(0) + IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); + synth.approve(address(_data.spotMarket), amount); + + (amount,) = _data.spotMarket.sell({ + marketId: _data.marketId, + synthAmount: amount, + minUsdAmount: _data.tolerance.tolerableSwapAmount, + referrer: _data.referrer }); - emit ZappedIn({amountWrapped: _amount, amountMinted: adjustedAmount}); + IERC20 sUSD = IERC20(_data.spotMarket.getSynth(USD_SPOT_MARKET_ID)); + sUSD.transfer(_data.receiver, amount); } - /*////////////////////////////////////////////////////////////// - ZAP OUT - //////////////////////////////////////////////////////////////*/ - - /// @notice unwrap $USDC from $sUSD - /// @dev call will result in $USDC transferred to this contract - /// @dev override this function to include additional logic - /// @dev unwrapping may result in a loss of precision: - /// unwrapping (1e12 + n) $sUSDC results in 1 $USDC - /// when n is a number less than 1e12; n $sUSDC is lost - /// @param _amount is the amount of $sUSD to sell - /// for $sUSDC and then unwrap - function _zapOut( - uint256 _amount - ) - internal - virtual - returns (uint256 adjustedAmount) - { - /// @notice prior to unwrapping, ensure that there - /// is enough $sUSDC to unwrap - /// @custom:example if $USDC has 6 decimals, and - /// $sUSDC has greater than 6 decimals, - /// then it is possible that the amount of - /// $sUSDC to unwrap is less than 1 $USDC; - /// this contract will prevent such cases - /// @dev if $USDC has 6 decimals, and $sUSDC has 18 decimals, - /// precision may be lost - if (_amount < _DECIMALS_FACTOR) { - revert InsufficientAmount(_amount); - } - - // allocate $sUSD allowance to the Spot Market Proxy - if (!_SUSD.approve(address(_SPOT_MARKET_PROXY), _amount)) { - revert ApprovalFailed( - address(_SUSD), - address(this), - address(_SPOT_MARKET_PROXY), - _amount - ); - } - - /// @notice buy $sUSDC with $sUSD - /// @dev call will result in $sUSDC minted/transferred - /// to the Zap contract - _SPOT_MARKET_PROXY.buy({ - marketId: _SUSDC_SPOT_MARKET_ID, - usdAmount: _amount, - minAmountReceived: _amount, - referrer: address(0) + function _zapOut(ZapData calldata _data) private { + uint256 amount = _data.amount; + + IERC20 sUSD = IERC20(_data.spotMarket.getSynth(USD_SPOT_MARKET_ID)); + sUSD.transferFrom(msg.sender, address(this), amount); + + sUSD.approve(address(_data.spotMarket), amount); + (amount,) = _data.spotMarket.buy({ + marketId: _data.marketId, + usdAmount: amount, + minAmountReceived: _data.tolerance.tolerableSwapAmount, + referrer: _data.referrer }); - // allocate $sUSDC allowance to the Spot Market Proxy - if (!_SUSDC.approve(address(_SPOT_MARKET_PROXY), _amount)) { - revert ApprovalFailed( - address(_SUSDC), - address(this), - address(_SPOT_MARKET_PROXY), - _amount - ); - } - - /// @notice $USDC might use non-standard decimals - /// @dev adjustedAmount is the amount of $USDC - /// expected to receive from unwrapping - /// @custom:example if $USDC has 6 decimals, - /// and $sUSD and $sUSDC have 18 decimals, - /// then, 1e12 $sUSD/$sUSDC = 1 $USDC - adjustedAmount = _amount / _DECIMALS_FACTOR; - - /// @notice unwrap $USDC via burning $sUSDC - /// @dev call will result in $USDC minted/transferred - /// to the Zap contract - _SPOT_MARKET_PROXY.unwrap({ - marketId: _SUSDC_SPOT_MARKET_ID, - unwrapAmount: _amount, - minAmountReceived: adjustedAmount + IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); + synth.approve(address(_data.spotMarket), amount); + + (amount,) = _data.spotMarket.unwrap({ + marketId: _data.marketId, + unwrapAmount: amount, + minAmountReceived: _data.tolerance.tolerableWrapAmount }); - emit ZappedOut({amountBurned: _amount, amountUnwrapped: adjustedAmount}); + _data.collateral.transfer(_data.receiver, amount); } } From f1378865516f0d3cf35b7bea4a76774c66550be3 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:24:18 -0400 Subject: [PATCH 008/129] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20remove=20old=20?= =?UTF-8?q?errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ZapErrors.sol | 54 ++--------------------------------------------- 1 file changed, 2 insertions(+), 52 deletions(-) diff --git a/src/ZapErrors.sol b/src/ZapErrors.sol index ad19fdc..04a5ba0 100644 --- a/src/ZapErrors.sol +++ b/src/ZapErrors.sol @@ -1,56 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; /// @title Zap contract errors /// @author JaredBorders (jaredborders@pm.me) -contract ZapErrors { - - /// @notice thrown when $USDC address is zero - /// @dev only can be thrown in during Zap deployment - error USDCZeroAddress(); - - /// @notice thrown when $sUSD address is zero - /// @dev only can be thrown in during Zap deployment - error SUSDZeroAddress(); - - /// @notice thrown when Synthetix v3 Spot Market Proxy - /// address is zero - /// @dev only can be thrown in during Zap deployment - error SpotMarketZeroAddress(); - - /// @notice thrown when the given Synthetix v3 Spot Market ID - /// for $sUSDC is incorrect; querying the Spot Market Proxy - /// contract for the name of the Spot Market ID returns - /// a different name than expected - /// @dev only can be thrown in during Zap deployment - /// @param id Synthetix v3 Spot Market ID for $sUSDC - error InvalidIdSUSDC(uint128 id); - - /// @notice thrown when a given token transfer fails - /// @param token address of the token contract - /// @param from address of the sender - /// @param to address of the recipient - /// @param amount amount of tokens to transfer - error TransferFailed( - address token, address from, address to, uint256 amount - ); - - /// @notice thrown when a given token approval fails - /// @param token address of the token contract - /// @param owner address of the token owner - /// @param spender address of the spender - /// @param amount amount of tokens to approve - error ApprovalFailed( - address token, address owner, address spender, uint256 amount - ); - - /// @notice thrown when the given amount is insufficient - /// due to decimals adjustment - /// @param amount amount of tokens to transfer - /// @custom:example if $USDC has 6 decimals, and - /// $sUSDC has greater than 6 decimals, - /// then it is possible that the amount of - /// $sUSDC to unwrap is less than 1 $USDC - error InsufficientAmount(uint256 amount); - -} +contract ZapErrors {} From 066547dd3d4cb0ccca41be3eab535fc4ffbd1dbf Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:24:31 -0400 Subject: [PATCH 009/129] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20remove=20old=20?= =?UTF-8?q?events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ZapEvents.sol | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/ZapEvents.sol b/src/ZapEvents.sol index 8923e52..862c923 100644 --- a/src/ZapEvents.sol +++ b/src/ZapEvents.sol @@ -1,18 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; /// @title Zap contract events /// @author JaredBorders (jaredborders@pm.me) -contract ZapEvents { - - /// @notice emitted after successful $USDC -> $sUSD zap - /// @param amountWrapped amount of $USDC wrapped - /// @param amountMinted amount of $sUSD minted - event ZappedIn(uint256 amountWrapped, uint256 amountMinted); - - /// @notice emitted after successful $sUSD -> $USDC zap - /// @param amountBurned amount of $sUSD burned - /// @param amountUnwrapped amount of $USDC unwrapped - event ZappedOut(uint256 amountBurned, uint256 amountUnwrapped); - -} +contract ZapEvents {} From 4eddef2cc83f8e7f41bb7774fc750e379d997d39 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:24:53 -0400 Subject: [PATCH 010/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20solc=20ve?= =?UTF-8?q?rsion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index 80650c9..76caaa8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,7 +3,7 @@ src = 'src' test = 'test/' out = 'out' libs = ['lib'] -solc_version = "0.8.20" +solc_version = "0.8.26" optimizer = true optimizer_runs = 1_000_000 From f2de3ae41b8e3d4b30644a9317d18aba0c70edb3 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:25:04 -0400 Subject: [PATCH 011/129] =?UTF-8?q?=F0=9F=93=9A=20update=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/README.md b/README.md index 3cd3c28..cbbd0df 100644 --- a/README.md +++ b/README.md @@ -11,20 +11,6 @@ [license]: https://opensource.org/license/GPL-3.0/ [license-badge]: https://img.shields.io/badge/GitHub-GPL--3.0-informational -## Contracts - -> `tree src/` - -``` -src/ -├── Zap.sol -├── ZapErrors.sol -├── ZapEvents.sol -└── interfaces - ├── IERC20.sol - └── ISpotMarketProxy.sol -``` - ## Tests 1. Follow the [Foundry guide to working on an existing project](https://book.getfoundry.sh/projects/working-on-an-existing-project.html) From 905b7cb56ab4fd35a982af8e38baf2bf1a515f28 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:25:30 -0400 Subject: [PATCH 012/129] =?UTF-8?q?=F0=9F=9A=80=20update=20deployment=20pa?= =?UTF-8?q?rams?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/parameters/ArbitrumParameters.sol | 20 ++----------------- .../parameters/ArbitrumSepoliaParameters.sol | 20 ++----------------- .../utils/parameters/BaseGoerliParameters.sol | 20 ------------------- script/utils/parameters/BaseParameters.sol | 20 ++----------------- .../parameters/BaseSepoliaParameters.sol | 4 ++++ .../parameters/OptimismGoerliParameters.sol | 14 ------------- .../utils/parameters/OptimismParameters.sol | 14 ++----------- .../parameters/OptimismSepoliaParameters.sol | 4 ++++ 8 files changed, 16 insertions(+), 100 deletions(-) delete mode 100644 script/utils/parameters/BaseGoerliParameters.sol create mode 100644 script/utils/parameters/BaseSepoliaParameters.sol delete mode 100644 script/utils/parameters/OptimismGoerliParameters.sol create mode 100644 script/utils/parameters/OptimismSepoliaParameters.sol diff --git a/script/utils/parameters/ArbitrumParameters.sol b/script/utils/parameters/ArbitrumParameters.sol index 82ff863..5d42f46 100644 --- a/script/utils/parameters/ArbitrumParameters.sol +++ b/script/utils/parameters/ArbitrumParameters.sol @@ -1,20 +1,4 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; -contract ArbitrumParameters { - - // https://arbiscan - address public constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; - - // https://arbiscan - address public constant SPOT_MARKET_PROXY = - 0x18141523403e2595D31b22604AcB8Fc06a4CaA61; - - // https://arbiscan - address public constant USD_PROXY = - 0x09d51516F38980035153a554c26Df3C6f51a23C3; - - // https://arbiscan - uint128 public constant SUSDC_SPOT_MARKET_ID = 1; - -} +contract ArbitrumParameters {} diff --git a/script/utils/parameters/ArbitrumSepoliaParameters.sol b/script/utils/parameters/ArbitrumSepoliaParameters.sol index 85547a9..1c3d4e2 100644 --- a/script/utils/parameters/ArbitrumSepoliaParameters.sol +++ b/script/utils/parameters/ArbitrumSepoliaParameters.sol @@ -1,20 +1,4 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; -contract ArbitrumSepoliaParameters { - - // https://arbiscan - address public constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; - - // https://arbiscan - address public constant SPOT_MARKET_PROXY = - 0x18141523403e2595D31b22604AcB8Fc06a4CaA61; - - // https://arbiscan - address public constant USD_PROXY = - 0x09d51516F38980035153a554c26Df3C6f51a23C3; - - // https://arbiscan - uint128 public constant SUSDC_SPOT_MARKET_ID = 1; - -} +contract ArbitrumSepoliaParameters {} diff --git a/script/utils/parameters/BaseGoerliParameters.sol b/script/utils/parameters/BaseGoerliParameters.sol deleted file mode 100644 index f86ea2c..0000000 --- a/script/utils/parameters/BaseGoerliParameters.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; - -contract BaseGoerliParameters { - - // https://goerli.basescan.org/token/0xf175520c52418dfe19c8098071a252da48cd1c19 - address public constant USDC = 0xF175520C52418dfE19C8098071a252da48Cd1C19; - - // https://usecannon.com/packages/synthetix-spot-market/3.3.5/84531-andromeda - address public constant SPOT_MARKET_PROXY = - 0x26f3EcFa0Aa924649cfd4b74C57637e910A983a4; - - // https://usecannon.com/packages/synthetix/3.3.5/84531-andromeda - address public constant USD_PROXY = - 0xa89163A087fe38022690C313b5D4BBF12574637f; - - // https://usecannon.com/packages/synthetix-spot-market/3.3.5/84531-andromeda - uint128 public constant SUSDC_SPOT_MARKET_ID = 1; - -} diff --git a/script/utils/parameters/BaseParameters.sol b/script/utils/parameters/BaseParameters.sol index 0728548..c00bf40 100644 --- a/script/utils/parameters/BaseParameters.sol +++ b/script/utils/parameters/BaseParameters.sol @@ -1,20 +1,4 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; -contract BaseParameters { - - // https://basescan.org/token/0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 - address public constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; - - // https://usecannon.com/packages/synthetix-spot-market/3.3.5/8453-andromeda - address public constant SPOT_MARKET_PROXY = - 0x18141523403e2595D31b22604AcB8Fc06a4CaA61; - - // https://usecannon.com/packages/synthetix/3.3.5/8453-andromeda - address public constant USD_PROXY = - 0x09d51516F38980035153a554c26Df3C6f51a23C3; - - // https://usecannon.com/packages/synthetix-spot-market/3.3.5/84531-andromeda - uint128 public constant SUSDC_SPOT_MARKET_ID = 1; - -} +contract BaseParameters {} diff --git a/script/utils/parameters/BaseSepoliaParameters.sol b/script/utils/parameters/BaseSepoliaParameters.sol new file mode 100644 index 0000000..426d4f9 --- /dev/null +++ b/script/utils/parameters/BaseSepoliaParameters.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +contract BaseSepoliaParameters {} diff --git a/script/utils/parameters/OptimismGoerliParameters.sol b/script/utils/parameters/OptimismGoerliParameters.sol deleted file mode 100644 index a4afe2d..0000000 --- a/script/utils/parameters/OptimismGoerliParameters.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; - -contract OptimismGoerliParameters { - - address public constant USDC = address(0); - - address public constant SPOT_MARKET_PROXY = address(0); - - address public constant USD_PROXY = address(0); - - uint128 public constant SUSDC_SPOT_MARKET_ID = type(uint128).max; - -} diff --git a/script/utils/parameters/OptimismParameters.sol b/script/utils/parameters/OptimismParameters.sol index ce27c84..198433b 100644 --- a/script/utils/parameters/OptimismParameters.sol +++ b/script/utils/parameters/OptimismParameters.sol @@ -1,14 +1,4 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; -contract OptimismParameters { - - address public constant USDC = address(0); - - address public constant SPOT_MARKET_PROXY = address(0); - - address public constant USD_PROXY = address(0); - - uint128 public constant SUSDC_SPOT_MARKET_ID = type(uint128).max; - -} +contract OptimismParameters {} diff --git a/script/utils/parameters/OptimismSepoliaParameters.sol b/script/utils/parameters/OptimismSepoliaParameters.sol new file mode 100644 index 0000000..f115de3 --- /dev/null +++ b/script/utils/parameters/OptimismSepoliaParameters.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +contract OptimismSepoliaParameters {} From 54f727646488e32d8b65b4b62b1d21f9118fa187 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:25:41 -0400 Subject: [PATCH 013/129] =?UTF-8?q?=F0=9F=9A=80=20update=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 76 +++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 54 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index f4d4819..6fde29e 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -1,35 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; import {Script} from "../lib/forge-std/src/Script.sol"; import {Zap} from "../src/Zap.sol"; -import {ZapExposed} from "../test/utils/exposed/ZapExposed.sol"; -import {ArbitrumParameters} from "./utils/parameters/ArbitrumParameters.sol"; -import {ArbitrumSepoliaParameters} from - "./utils/parameters/ArbitrumSepoliaParameters.sol"; -import {BaseGoerliParameters} from "./utils/parameters/BaseGoerliParameters.sol"; -import {BaseParameters} from "./utils/parameters/BaseParameters.sol"; -import {OptimismGoerliParameters} from - "./utils/parameters/OptimismGoerliParameters.sol"; -import {OptimismParameters} from "./utils/parameters/OptimismParameters.sol"; /// @title Zap deployment script -/// @notice ZapExposed is deployed (not Zap) and -/// ZapExposed is unsafe and not meant for production /// @author JaredBorders (jaredborders@pm.me) contract Setup is Script { - function deploySystem( - address _usdc, - address _susd, - address _spotMarketProxy, - uint128 _sUSDCId - ) - public - returns (address zapAddress) - { - zapAddress = - address(new ZapExposed(_usdc, _susd, _spotMarketProxy, _sUSDCId)); + function deploySystem() public returns (address zap) { + zap = address(new Zap()); } } @@ -38,35 +18,31 @@ contract Setup is Script { /// (1) load the variables in the .env file via `source .env` /// (2) run `forge script script/Deploy.s.sol:DeployBase --rpc-url $BASE_RPC_URL /// --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv` -contract DeployBase is Setup, BaseParameters { +contract DeployBase is Setup { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - Setup.deploySystem( - USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID - ); + Setup.deploySystem(); vm.stopBroadcast(); } } -/// @dev steps to deploy and verify on Base Goerli: +/// @dev steps to deploy and verify on Base Sepolia: /// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployBaseGoerli --rpc-url -/// $BASE_GOERLI_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast +/// (2) run `forge script script/Deploy.s.sol:DeployBaseSepolia --rpc-url +/// $BASE_SEPOLIA_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast /// --verify -vvvv` -contract DeployBaseGoerli is Setup, BaseGoerliParameters { +contract DeployBaseSepolia is Setup { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - Setup.deploySystem( - USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID - ); + Setup.deploySystem(); vm.stopBroadcast(); } @@ -78,35 +54,31 @@ contract DeployBaseGoerli is Setup, BaseGoerliParameters { /// (2) run `forge script script/Deploy.s.sol:DeployOptimism --rpc-url /// $OPTIMISM_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY /// --broadcast --verify -vvvv` -contract DeployOptimism is Setup, OptimismParameters { +contract DeployOptimism is Setup { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - Setup.deploySystem( - USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID - ); + Setup.deploySystem(); vm.stopBroadcast(); } } -/// @dev steps to deploy and verify on Optimism Goerli: +/// @dev steps to deploy and verify on Optimism Sepolia: /// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployOptimismGoerli --rpc-url -/// $OPTIMISM_GOERLI_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY +/// (2) run `forge script script/Deploy.s.sol:DeployOptimismSepolia --rpc-url +/// $OPTIMISM_SEPOLIA_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY /// --broadcast --verify -vvvv` -contract DeployOptimismGoerli is Setup, OptimismGoerliParameters { +contract DeployOptimismSepolia is Setup { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - Setup.deploySystem( - USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID - ); + Setup.deploySystem(); vm.stopBroadcast(); } @@ -116,17 +88,15 @@ contract DeployOptimismGoerli is Setup, OptimismGoerliParameters { /// @dev steps to deploy and verify on Arbitrum: /// (1) load the variables in the .env file via `source .env` /// (2) run `forge script script/Deploy.s.sol:DeployArbitrum --rpc-url -/// $ARBITRUM_GOERLI_RPC_URL --etherscan-api-key $ARBITRUM_GOERLI_RPC_URL +/// $ARBITRUM_RPC_URL --etherscan-api-key $ARBITRUM_RPC_URL /// --broadcast --verify -vvvv` -contract DeployArbitrum is Setup, ArbitrumParameters { +contract DeployArbitrum is Setup { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - Setup.deploySystem( - USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID - ); + Setup.deploySystem(); vm.stopBroadcast(); } @@ -138,15 +108,13 @@ contract DeployArbitrum is Setup, ArbitrumParameters { /// (2) run `forge script script/Deploy.s.sol:DeployArbitrumSepolia --rpc-url /// $ARBITRUM_SEPOLIA_RPC_URL --etherscan-api-key $ARBITRUM_SEPOLIA_RPC_URL /// --broadcast --verify -vvvv` -contract DeployArbitrumSepolia is Setup, ArbitrumSepoliaParameters { +contract DeployArbitrumSepolia is Setup { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - Setup.deploySystem( - USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID - ); + Setup.deploySystem(); vm.stopBroadcast(); } From 85f84f57ed68a2ffc32f1e168ce3ee725e714d64 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:26:12 -0400 Subject: [PATCH 014/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20bump=20solc=20vers?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/IERC20.sol | 2 +- src/interfaces/ISpotMarketProxy.sol | 2 +- test/utils/errors/SynthetixV3Errors.sol | 5 +---- test/utils/mocks/MockSpotMarketProxy.sol | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 85f896d..8bd6976 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; /// @title Reduced Interface of the ERC20 standard as defined in the EIP /// @author OpenZeppelin diff --git a/src/interfaces/ISpotMarketProxy.sol b/src/interfaces/ISpotMarketProxy.sol index 1a6c3fb..18c9478 100644 --- a/src/interfaces/ISpotMarketProxy.sol +++ b/src/interfaces/ISpotMarketProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; /// @title Consolidated Spot Market Proxy Interface /// @notice Responsible for interacting with Synthetix v3 spot markets diff --git a/test/utils/errors/SynthetixV3Errors.sol b/test/utils/errors/SynthetixV3Errors.sol index 865b8dc..4a64d36 100644 --- a/test/utils/errors/SynthetixV3Errors.sol +++ b/test/utils/errors/SynthetixV3Errors.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; /// @title Cosolidated Errors from Synthetix v3 contracts -/// @notice This contract consolidates all necessary errors from Synthetix v3 -/// contracts -/// and is used for testing purposes /// @author JaredBorders (jaredborders@pm.me) contract SynthetixV3Errors { diff --git a/test/utils/mocks/MockSpotMarketProxy.sol b/test/utils/mocks/MockSpotMarketProxy.sol index 62e025a..cfd5338 100644 --- a/test/utils/mocks/MockSpotMarketProxy.sol +++ b/test/utils/mocks/MockSpotMarketProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.26; import {ISpotMarketProxy} from "./../../../src/interfaces/ISpotMarketProxy.sol"; From 5dd49f97b7aa66d1415582d52b8fd0b83dd0400d Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:26:38 -0400 Subject: [PATCH 015/129] =?UTF-8?q?=E2=9C=85=20refactor=20test=20suite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Zap.In.t.sol | 19 ++ test/Zap.Out.t.sol | 19 ++ test/Zap.t.sol | 410 --------------------------------------- test/utils/Bootstrap.sol | 83 -------- test/utils/Constants.sol | 16 -- 5 files changed, 38 insertions(+), 509 deletions(-) create mode 100644 test/Zap.In.t.sol create mode 100644 test/Zap.Out.t.sol delete mode 100644 test/Zap.t.sol delete mode 100644 test/utils/Bootstrap.sol delete mode 100644 test/utils/Constants.sol diff --git a/test/Zap.In.t.sol b/test/Zap.In.t.sol new file mode 100644 index 0000000..366afa9 --- /dev/null +++ b/test/Zap.In.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +import {Zap} from "../src/Zap.sol"; +import {Test} from "forge-std/Test.sol"; + +contract ZapInTest is Test { + + Zap zap; + + function setUp() public { + zap = new Zap(); + } + + function test_zap_in() public { + assertTrue(true); + } + +} diff --git a/test/Zap.Out.t.sol b/test/Zap.Out.t.sol new file mode 100644 index 0000000..980607f --- /dev/null +++ b/test/Zap.Out.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +import {Zap} from "../src/Zap.sol"; +import {Test} from "forge-std/Test.sol"; + +contract ZapOutTest is Test { + + Zap zap; + + function setUp() public { + zap = new Zap(); + } + + function test_zap_out() public { + assertTrue(true); + } + +} diff --git a/test/Zap.t.sol b/test/Zap.t.sol deleted file mode 100644 index c1a6a90..0000000 --- a/test/Zap.t.sol +++ /dev/null @@ -1,410 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; - -import {ZapErrors} from "../src/ZapErrors.sol"; -import {IERC20} from "../src/interfaces/IERC20.sol"; -import {ISpotMarketProxy} from "../src/interfaces/ISpotMarketProxy.sol"; -import { - Bootstrap, - MockSUSD, - MockSpotMarketProxy, - MockUSDC -} from "./utils/Bootstrap.sol"; - -import {ZapExposed} from "./utils/exposed/ZapExposed.sol"; - -/** - * @custom:example-wrap-and-unwrap - * - * 1 $USDC - * ^ - * | - * | - * Synthetix Spot Market: Wrap --> 1e12 $sUSDC - * | - * | - * Synthetix Spot Market: Sell - * | - * | - * v - * 1e12 $sUSD - * - * @dev - * - * IF $USDC has 6 decimals - * AND $sUSD and $sUSDC have 18 decimals - * THEN 1e12 $sUSD/$sUSDC = 1 $USDC - * AND in the context of this system, the DECIMAL_FACTOR would be: 10^(18-6) == - * 1e12 - */ -contract ZapTest is Bootstrap { - - IERC20 internal SUSD; - IERC20 internal USDC; - IERC20 internal SUSDC; - ISpotMarketProxy internal SPOT_MARKET_PROXY; - - MockSpotMarketProxy public mockSpotMarketProxy; - MockUSDC public mockUSDC; - MockSUSD public mockSUSD; - - uint256 internal DECIMAL_FACTOR; - - function setUp() public { - vm.rollFork(BASE_BLOCK_NUMBER); - initializeBase(); - - SUSD = IERC20(zap.expose_SUSD()); - USDC = IERC20(zap.expose_USDC()); - SUSDC = IERC20(zap.expose_SUSDC()); - SPOT_MARKET_PROXY = ISpotMarketProxy(zap.expose_SPOT_MARKET_PROXY()); - - mockSpotMarketProxy = new MockSpotMarketProxy(); - mockUSDC = new MockUSDC(); - mockSUSD = new MockSUSD(); - - deal(address(SUSD), ACTOR, INITIAL_MINT); - deal(address(USDC), ACTOR, INITIAL_MINT); - - vm.startPrank(ACTOR); - - USDC.approve(address(zap), type(uint256).max); - SUSD.approve(address(zap), type(uint256).max); - - vm.stopPrank(); - - DECIMAL_FACTOR = zap.expose_DECIMALS_FACTOR(); - } - -} - -contract Deployment is ZapTest { - - function test_zap_address() public view { - assert(address(zap) != address(0)); - } - - function test_hashed_susdc_name() public { - assertEq( - zap.expose_HASHED_SUSDC_NAME(), - keccak256(abi.encodePacked("Synthetic USD Coin Spot Market")) - ); - } - - function test_usdc_zero_address() public { - vm.expectRevert( - abi.encodeWithSelector(ZapErrors.USDCZeroAddress.selector) - ); - - new ZapExposed({ - _usdc: address(0), - _susd: address(mockSUSD), - _spotMarketProxy: address(mockSpotMarketProxy), - _sUSDCId: type(uint128).max - }); - } - - function test_susd_zero_address() public { - vm.expectRevert( - abi.encodeWithSelector(ZapErrors.SUSDZeroAddress.selector) - ); - - new ZapExposed({ - _usdc: address(mockUSDC), - _susd: address(0), - _spotMarketProxy: address(mockSpotMarketProxy), - _sUSDCId: type(uint128).max - }); - } - - function test_spotMarketProxy_zero_address() public { - vm.expectRevert( - abi.encodeWithSelector(ZapErrors.SpotMarketZeroAddress.selector) - ); - - new ZapExposed({ - _usdc: address(mockUSDC), - _susd: address(mockSUSD), - _spotMarketProxy: address(0), - _sUSDCId: type(uint128).max - }); - } - - function test_usdc_decimals_factor() public view { - assert(zap.expose_DECIMALS_FACTOR() != 0); - } - - function test_sUSDCId_invalid() public { - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.InvalidIdSUSDC.selector, type(uint128).max - ) - ); - - new ZapExposed({ - _usdc: address(mockUSDC), - _susd: address(mockSUSD), - _spotMarketProxy: address(mockSpotMarketProxy), - _sUSDCId: type(uint128).max - }); - } - - function test_sUSDC_address() public view { - assert(zap.expose_SUSDC() != address(0)); - } - -} - -contract ZapIn is ZapTest { - - function test_zap_in() public { - vm.startPrank(ACTOR); - - zap.expose_zapIn(AMOUNT); - - assertEq(SUSD.balanceOf(address(zap)), AMOUNT * DECIMAL_FACTOR); - - vm.stopPrank(); - } - - function test_zap_in_zero() public { - vm.startPrank(ACTOR); - - vm.expectRevert(); - - zap.expose_zapIn(0); - - vm.stopPrank(); - } - - function test_zap_in_exceeds_cap() public { - vm.startPrank(ACTOR); - - try zap.expose_zapIn(AMOUNT * 10) {} - catch (bytes memory reason) { - // (AMOUNT * 10) exceeds cap at the given BASE_BLOCK_NUMBER - assertEq(bytes4(reason), WrapperExceedsMaxAmount.selector); - } - - vm.stopPrank(); - } - - function test_zap_in_usdc_transfer_fails() public { - vm.mockCall( - address(USDC), - abi.encodeWithSelector( - IERC20.transferFrom.selector, ACTOR, address(zap), AMOUNT - ), - abi.encode(false) - ); - - vm.startPrank(ACTOR); - - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.TransferFailed.selector, - address(USDC), - ACTOR, - address(zap), - AMOUNT - ) - ); - - zap.expose_zapIn(AMOUNT); - - vm.stopPrank(); - } - - function test_zap_in_usdc_approve_fails() public { - vm.mockCall( - address(USDC), - abi.encodeWithSelector( - IERC20.approve.selector, address(SPOT_MARKET_PROXY), AMOUNT - ), - abi.encode(false) - ); - - vm.startPrank(ACTOR); - - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.ApprovalFailed.selector, - address(USDC), - address(zap), - address(SPOT_MARKET_PROXY), - AMOUNT - ) - ); - - zap.expose_zapIn(AMOUNT); - - vm.stopPrank(); - } - - function test_zap_in_susdc_approve_fails() public { - vm.mockCall( - address(SUSDC), - abi.encodeWithSelector( - IERC20.approve.selector, - address(SPOT_MARKET_PROXY), - AMOUNT * DECIMAL_FACTOR - ), - abi.encode(false) - ); - - vm.startPrank(ACTOR); - - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.ApprovalFailed.selector, - address(SUSDC), - address(zap), - address(SPOT_MARKET_PROXY), - AMOUNT * DECIMAL_FACTOR - ) - ); - - zap.expose_zapIn(AMOUNT); - - vm.stopPrank(); - } - - function test_zap_in_event() public { - vm.startPrank(ACTOR); - - vm.expectEmit(true, true, true, true); - emit ZappedIn(AMOUNT, AMOUNT * DECIMAL_FACTOR); - - zap.expose_zapIn(AMOUNT); - - vm.stopPrank(); - } - -} - -contract ZapOut is ZapTest { - - function test_zap_out() public { - vm.startPrank(ACTOR); - - SUSD.transfer(address(zap), AMOUNT * DECIMAL_FACTOR); - - zap.expose_zapOut(AMOUNT * DECIMAL_FACTOR); - - assertEq(USDC.balanceOf(address(zap)), AMOUNT); - - vm.stopPrank(); - } - - function test_zap_out_zero() public { - vm.startPrank(ACTOR); - - vm.expectRevert(); - - zap.expose_zapOut(0); - - vm.stopPrank(); - } - - function test_zap_out_susd_approve_fails() public { - vm.mockCall( - address(SUSD), - abi.encodeWithSelector( - IERC20.approve.selector, - address(SPOT_MARKET_PROXY), - AMOUNT * DECIMAL_FACTOR - ), - abi.encode(false) - ); - - vm.startPrank(ACTOR); - - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.ApprovalFailed.selector, - address(SUSD), - address(zap), - address(SPOT_MARKET_PROXY), - AMOUNT * DECIMAL_FACTOR - ) - ); - - zap.expose_zapOut((AMOUNT * DECIMAL_FACTOR)); - - vm.stopPrank(); - } - - function test_zap_out_susdc_approve_fails() public { - vm.mockCall( - address(SUSDC), - abi.encodeWithSelector( - IERC20.approve.selector, - address(SPOT_MARKET_PROXY), - AMOUNT * DECIMAL_FACTOR - ), - abi.encode(false) - ); - - vm.startPrank(ACTOR); - - SUSD.transfer(address(zap), AMOUNT * DECIMAL_FACTOR); - - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.ApprovalFailed.selector, - address(SUSDC), - address(zap), - address(SPOT_MARKET_PROXY), - AMOUNT * DECIMAL_FACTOR - ) - ); - - zap.expose_zapOut((AMOUNT * DECIMAL_FACTOR)); - - vm.stopPrank(); - } - - function test_zap_out_insufficient_amount() public { - vm.startPrank(ACTOR); - - SUSD.transfer(address(zap), AMOUNT * DECIMAL_FACTOR); - - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.InsufficientAmount.selector, DECIMAL_FACTOR - 1 - ) - ); - - zap.expose_zapOut(DECIMAL_FACTOR - 1); - - vm.stopPrank(); - } - - function test_zap_out_precision_loss(uint256 sUSDLost) public { - vm.assume(sUSDLost < DECIMAL_FACTOR); - - vm.startPrank(ACTOR); - - SUSD.transfer(address(zap), (AMOUNT * DECIMAL_FACTOR) + sUSDLost); - - zap.expose_zapOut(AMOUNT * DECIMAL_FACTOR + sUSDLost); - - assertEq(USDC.balanceOf(address(zap)), AMOUNT); - - vm.stopPrank(); - } - - function test_zap_out_event() public { - vm.startPrank(ACTOR); - - SUSD.transfer(address(zap), AMOUNT * DECIMAL_FACTOR); - - vm.expectEmit(true, true, true, true); - emit ZappedOut(AMOUNT * DECIMAL_FACTOR, AMOUNT); - - zap.expose_zapOut((AMOUNT * DECIMAL_FACTOR)); - - vm.stopPrank(); - } - -} diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol deleted file mode 100644 index 49269c3..0000000 --- a/test/utils/Bootstrap.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; - -import {Test} from "../../lib/forge-std/src/Test.sol"; -import {console2} from "../../lib/forge-std/src/console2.sol"; -import { - BaseGoerliParameters, - BaseParameters, - Setup, - Zap -} from "../../script/Deploy.s.sol"; -import {ZapExposed} from "../utils/exposed/ZapExposed.sol"; -import {MockSUSD} from "../utils/mocks/MockSUSD.sol"; -import {MockSpotMarketProxy} from "../utils/mocks/MockSpotMarketProxy.sol"; -import {MockUSDC} from "../utils/mocks/MockUSDC.sol"; -import {ZapEvents} from "./../../src/ZapEvents.sol"; -import {Constants} from "./Constants.sol"; - -import {SynthetixV3Errors} from "./errors/SynthetixV3Errors.sol"; - -/// @title Bootstrap contract for setting up test environment -/// @author JaredBorders (jaredborders@pm.me) -contract Bootstrap is Test, ZapEvents, SynthetixV3Errors, Constants { - - using console2 for *; - - ZapExposed public zap; - - function initializeLocal() internal { - BootstrapLocal bootstrap = new BootstrapLocal(); - (address zapAddress) = bootstrap.init(); - - zap = ZapExposed(zapAddress); - } - - function initializeBase() internal { - BootstrapBase bootstrap = new BootstrapBase(); - (address zapAddress) = bootstrap.init(); - - zap = ZapExposed(zapAddress); - } - - function initializeBaseGoerli() internal { - BootstrapBaseGoerli bootstrap = new BootstrapBaseGoerli(); - (address zapAddress) = bootstrap.init(); - - zap = ZapExposed(zapAddress); - } - -} - -contract BootstrapLocal is Setup, Constants { - - function init() public returns (address zapAddress) { - zapAddress = Setup.deploySystem( - address(new MockUSDC()), - address(new MockSUSD()), - address(new MockSpotMarketProxy()), - type(uint128).max - ); - } - -} - -contract BootstrapBase is Setup, BaseParameters { - - function init() public returns (address zapAddress) { - zapAddress = Setup.deploySystem( - USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID - ); - } - -} - -contract BootstrapBaseGoerli is Setup, BaseGoerliParameters { - - function init() public returns (address zapAddress) { - zapAddress = Setup.deploySystem( - USDC, USD_PROXY, SPOT_MARKET_PROXY, SUSDC_SPOT_MARKET_ID - ); - } - -} diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol deleted file mode 100644 index 7d7a056..0000000 --- a/test/utils/Constants.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; - -/// @title Contract for defining constants used in testing -/// @author JaredBorders (jaredborders@pm.me) -contract Constants { - - uint256 public constant BASE_BLOCK_NUMBER = 8_163_300; - - address public constant ACTOR = address(0xAC7AC7AC7); - - uint256 public constant INITIAL_MINT = type(uint256).max / 10; - - uint256 public constant AMOUNT = 1000; - -} From 7524c9992d052093715487f6adfdbe1d8d037793 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:26:55 -0400 Subject: [PATCH 016/129] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20remove=20old=20?= =?UTF-8?q?snapshot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .gas-snapshot diff --git a/.gas-snapshot b/.gas-snapshot deleted file mode 100644 index d688322..0000000 --- a/.gas-snapshot +++ /dev/null @@ -1,22 +0,0 @@ -Deployment:test_hashed_susdc_name() (gas: 5568) -Deployment:test_sUSDCId_invalid() (gas: 51853) -Deployment:test_sUSDC_address() (gas: 5455) -Deployment:test_spotMarketProxy_zero_address() (gas: 41709) -Deployment:test_susd_zero_address() (gas: 41696) -Deployment:test_usdc_decimals_factor() (gas: 5420) -Deployment:test_usdc_zero_address() (gas: 41637) -Deployment:test_zap_address() (gas: 2356) -ZapIn:test_zap_in() (gas: 463863) -ZapIn:test_zap_in_event() (gas: 461040) -ZapIn:test_zap_in_exceeds_cap() (gas: 457276) -ZapIn:test_zap_in_susdc_approve_fails() (gas: 301777) -ZapIn:test_zap_in_usdc_approve_fails() (gas: 62939) -ZapIn:test_zap_in_usdc_transfer_fails() (gas: 14044) -ZapIn:test_zap_in_zero() (gas: 246817) -ZapOut:test_zap_out() (gas: 465470) -ZapOut:test_zap_out_event() (gas: 463923) -ZapOut:test_zap_out_insufficient_amount() (gas: 55830) -ZapOut:test_zap_out_precision_loss(uint256) (runs: 256, μ: 465998, ~: 465998) -ZapOut:test_zap_out_susd_approve_fails() (gas: 18892) -ZapOut:test_zap_out_susdc_approve_fails() (gas: 312592) -ZapOut:test_zap_out_zero() (gas: 9086) \ No newline at end of file From 5eb7e947bf572dcfbfe0a1fb0bc8347413667e5c Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:27:09 -0400 Subject: [PATCH 017/129] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20remove=20old=20?= =?UTF-8?q?exposed=20zap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/utils/exposed/ZapExposed.sol | 57 ------------------------------- 1 file changed, 57 deletions(-) delete mode 100644 test/utils/exposed/ZapExposed.sol diff --git a/test/utils/exposed/ZapExposed.sol b/test/utils/exposed/ZapExposed.sol deleted file mode 100644 index 31473de..0000000 --- a/test/utils/exposed/ZapExposed.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; - -import {Zap} from "../../../src/Zap.sol"; - -/// @title Exposed Zap contract used for testing and in the deploy script -/// @dev although it is used in the deploy script, it is -/// *not* safe and *not* meant for mainnet -/// @author JaredBorders (jaredborders@pm.me) -contract ZapExposed is Zap { - - function expose_HASHED_SUSDC_NAME() public pure returns (bytes32) { - return _HASHED_SUSDC_NAME; - } - - function expose_USDC() public view returns (address) { - return address(_USDC); - } - - function expose_SUSD() public view returns (address) { - return address(_SUSD); - } - - function expose_SUSDC() public view returns (address) { - return address(_SUSDC); - } - - function expose_SUSDC_SPOT_MARKET_ID() public view returns (uint128) { - return _SUSDC_SPOT_MARKET_ID; - } - - function expose_SPOT_MARKET_PROXY() public view returns (address) { - return address(_SPOT_MARKET_PROXY); - } - - function expose_DECIMALS_FACTOR() public view returns (uint256) { - return _DECIMALS_FACTOR; - } - - constructor( - address _usdc, - address _susd, - address _spotMarketProxy, - uint128 _sUSDCId - ) - Zap(_usdc, _susd, _spotMarketProxy, _sUSDCId) - {} - - function expose_zapIn(uint256 _amount) public { - _zapIn(_amount); - } - - function expose_zapOut(uint256 _amount) public { - _zapOut(_amount); - } - -} From 4804c80c494d6aef1ccd8128f64e92a0e5609ba0 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:27:22 -0400 Subject: [PATCH 018/129] =?UTF-8?q?=F0=9F=8E=AD=20update=20mocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/utils/mocks/MockERC20.sol | 87 ++++++++++++++++++++++++++++++++++ test/utils/mocks/MockSUSD.sol | 39 --------------- test/utils/mocks/MockUSDC.sol | 39 --------------- 3 files changed, 87 insertions(+), 78 deletions(-) create mode 100644 test/utils/mocks/MockERC20.sol delete mode 100644 test/utils/mocks/MockSUSD.sol delete mode 100644 test/utils/mocks/MockUSDC.sol diff --git a/test/utils/mocks/MockERC20.sol b/test/utils/mocks/MockERC20.sol new file mode 100644 index 0000000..bd75508 --- /dev/null +++ b/test/utils/mocks/MockERC20.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +import {IERC20} from "./../../../src/interfaces/IERC20.sol"; + +/// @title MockERC20 contract for testing +/// @dev Implements ERC20 standard with extra state modification functions +/// @author JaredBorders (jaredborders@pm.me) +contract MockERC20 is IERC20 { + + string public name; + string public symbol; + uint8 public decimals; + uint256 public totalSupply; + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + constructor(string memory _name, string memory _symbol, uint8 _decimals) { + name = _name; + symbol = _symbol; + decimals = _decimals; + } + + function approve( + address spender, + uint256 amount + ) + public + override + returns (bool) + { + allowance[msg.sender][spender] = amount; + return true; + } + + function transfer( + address to, + uint256 amount + ) + public + override + returns (bool) + { + balanceOf[msg.sender] -= amount; + balanceOf[to] += amount; + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) + public + override + returns (bool) + { + uint256 allowed = allowance[from][msg.sender]; + if (allowed != type(uint256).max) { + allowance[from][msg.sender] = allowed - amount; + } + balanceOf[from] -= amount; + balanceOf[to] += amount; + return true; + } + + function mint(address to, uint256 amount) public { + totalSupply += amount; + balanceOf[to] += amount; + } + + function burn(address from, uint256 amount) public { + balanceOf[from] -= amount; + totalSupply -= amount; + } + + function setAllowance( + address owner, + address spender, + uint256 amount + ) + public + { + allowance[owner][spender] = amount; + } + +} diff --git a/test/utils/mocks/MockSUSD.sol b/test/utils/mocks/MockSUSD.sol deleted file mode 100644 index c4b573c..0000000 --- a/test/utils/mocks/MockSUSD.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; - -import {IERC20} from "./../../../src/interfaces/IERC20.sol"; - -/// @title MockSUSD contract for testing -/// @author JaredBorders (jaredborders@pm.me) -contract MockSUSD is IERC20 { - - function decimals() external pure override returns (uint8) { - return 18; - } - - function balanceOf(address) external pure override returns (uint256) { - return 0; - } - - function transfer(address, uint256) external pure override returns (bool) { - return true; - } - - function approve(address, uint256) external pure override returns (bool) { - return true; - } - - function transferFrom( - address, - address, - uint256 - ) - external - pure - override - returns (bool) - { - return true; - } - -} diff --git a/test/utils/mocks/MockUSDC.sol b/test/utils/mocks/MockUSDC.sol deleted file mode 100644 index c3f220a..0000000 --- a/test/utils/mocks/MockUSDC.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.20; - -import {IERC20} from "./../../../src/interfaces/IERC20.sol"; - -/// @title MockSUSD contract for testing -/// @author JaredBorders (jaredborders@pm.me) -contract MockUSDC is IERC20 { - - function decimals() external pure override returns (uint8) { - return 6; - } - - function balanceOf(address) external pure override returns (uint256) { - return 0; - } - - function transfer(address, uint256) external pure override returns (bool) { - return true; - } - - function approve(address, uint256) external pure override returns (bool) { - return true; - } - - function transferFrom( - address, - address, - uint256 - ) - external - pure - override - returns (bool) - { - return true; - } - -} From fd02d3a49e9ff9dc1df3e779d80932c43bd5ec1b Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:24:21 -0400 Subject: [PATCH 019/129] =?UTF-8?q?=F0=9F=94=A5=20burndown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Zap.sol b/src/Zap.sol index 50e4c07..21d388f 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -10,6 +10,29 @@ import {ISpotMarketProxy} from "./interfaces/ISpotMarketProxy.sol"; /// @author JaredBorders (jaredborders@pm.me) contract Zap is ZapErrors, ZapEvents { + /** + 1. Docs + 2. Errors + - Unsupported market + - Unsupported collateral + - Try-Catch Pattern + 3. Events + 4. Tests + 5. Update SMv3 to + - Define mutable address pointing to Zap.sol + - Define getter/setter for Zap.sol + - Update or Modify or Add new functions that use old Zap fucntionalities + 6. "Upgrade" SMv3 to use Zap.sol + - Deploy Zap.sol + - Run upgrade script for SMv3 to get new Implementation + - Call UUPS upgradeToAndCall() with new implementation and data + 7. (future) Update Zap to include flashloans + 8. (future) Deploy new Zap contract with flashloans + 9. (future) Call SMv3's Zap setter to point to new Zap contract + - (steps 7-9 are used for any future Zap changes) + */ + + uint128 internal constant USD_SPOT_MARKET_ID = 0; enum Direction { From ee6db942d963c9c05469e7f39404d2bd6ced1431 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 17 Sep 2024 15:51:34 +0200 Subject: [PATCH 020/129] =?UTF-8?q?=F0=9F=91=B7=20Add=20Natspec=20custom?= =?UTF-8?q?=20errors=20and=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 147 +++++++++++++++++++++++++++++++++++++--------- src/ZapErrors.sol | 16 ++++- src/ZapEvents.sol | 62 ++++++++++++++++++- 3 files changed, 195 insertions(+), 30 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 21d388f..09b5152 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -11,40 +11,41 @@ import {ISpotMarketProxy} from "./interfaces/ISpotMarketProxy.sol"; contract Zap is ZapErrors, ZapEvents { /** - 1. Docs - 2. Errors - - Unsupported market - - Unsupported collateral - - Try-Catch Pattern - 3. Events - 4. Tests - 5. Update SMv3 to - - Define mutable address pointing to Zap.sol - - Define getter/setter for Zap.sol - - Update or Modify or Add new functions that use old Zap fucntionalities - 6. "Upgrade" SMv3 to use Zap.sol - - Deploy Zap.sol - - Run upgrade script for SMv3 to get new Implementation - - Call UUPS upgradeToAndCall() with new implementation and data - 7. (future) Update Zap to include flashloans - 8. (future) Deploy new Zap contract with flashloans - 9. (future) Call SMv3's Zap setter to point to new Zap contract - - (steps 7-9 are used for any future Zap changes) + * 2. Errors + * - Unsupported market + * - Unsupported collateral + * - Try-Catch Pattern + * 4. Tests */ - + /// @notice Constant for the USD spot market ID uint128 internal constant USD_SPOT_MARKET_ID = 0; + /// @notice Enum to specify the direction of the zap operation + /// @dev In: Collateral to sUSD, Out: sUSD to Collateral enum Direction { In, Out } + /// @notice Struct to define tolerance levels for wrap and swap operations + /// @param tolerableWrapAmount Minimum acceptable amount for wrap/unwrap + /// operations + /// @param tolerableSwapAmount Minimum acceptable amount for swap operations struct Tolerance { uint256 tolerableWrapAmount; uint256 tolerableSwapAmount; } + /// @notice Struct containing all necessary data for a zap operation + /// @param spotMarket Address of the spot market proxy contract + /// @param collateral Address of the collateral token + /// @param marketId ID of the target market + /// @param amount Amount of tokens to zap + /// @param tolerance Tolerance struct for minimum acceptable amounts + /// @param direction Direction of the zap (In or Out) + /// @param receiver Address to receive the output tokens + /// @param referrer Address of the referrer struct ZapData { ISpotMarketProxy spotMarket; IERC20 collateral; @@ -56,36 +57,56 @@ contract Zap is ZapErrors, ZapEvents { address referrer; } + /// @notice Main function to perform a zap operation + /// @param _data ZapData struct containing all necessary information function zap(ZapData calldata _data) external { _data.direction == Direction.In ? _zapIn(_data) : _zapOut(_data); } + /// @notice Internal function to perform a zap-in operation (Collateral to + /// sUSD) + /// @param _data ZapData struct containing all necessary information function _zapIn(ZapData calldata _data) private { uint256 amount = _data.amount; _data.collateral.transferFrom(msg.sender, address(this), amount); _data.collateral.approve(address(_data.spotMarket), amount); - (amount,) = _data.spotMarket.wrap({ + try _data.spotMarket.wrap({ marketId: _data.marketId, wrapAmount: amount, minAmountReceived: _data.tolerance.tolerableWrapAmount - }); + }) returns (uint256 amountToMint, ISpotMarketProxy.Data memory) { + amount = amountToMint; + } catch Error(string memory reason) { + revert WrapFailed(reason); + } IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); synth.approve(address(_data.spotMarket), amount); - (amount,) = _data.spotMarket.sell({ + try _data.spotMarket.sell({ marketId: _data.marketId, synthAmount: amount, minUsdAmount: _data.tolerance.tolerableSwapAmount, referrer: _data.referrer - }); + }) returns (uint256 usdAmountReceived, ISpotMarketProxy.Data memory) { + amount = usdAmountReceived; + } catch Error(string memory reason) { + revert SwapFailed(reason); + } IERC20 sUSD = IERC20(_data.spotMarket.getSynth(USD_SPOT_MARKET_ID)); sUSD.transfer(_data.receiver, amount); + + emit ZapIn( + msg.sender, _data.marketId, _data.amount, amount, _data.receiver + ); } + /// @notice Internal function to perform a zap-out operation (sUSD to + /// Collateral) + /// @param _data ZapData struct containing all necessary information function _zapOut(ZapData calldata _data) private { uint256 amount = _data.amount; @@ -93,23 +114,93 @@ contract Zap is ZapErrors, ZapEvents { sUSD.transferFrom(msg.sender, address(this), amount); sUSD.approve(address(_data.spotMarket), amount); - (amount,) = _data.spotMarket.buy({ + + try _data.spotMarket.buy({ marketId: _data.marketId, usdAmount: amount, minAmountReceived: _data.tolerance.tolerableSwapAmount, referrer: _data.referrer - }); + }) returns (uint256 synthAmount, ISpotMarketProxy.Data memory) { + amount = synthAmount; + } catch Error(string memory reason) { + revert SwapFailed(reason); + } + + IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); + synth.approve(address(_data.spotMarket), amount); + + try _data.spotMarket.unwrap({ + marketId: _data.marketId, + unwrapAmount: amount, + minAmountReceived: _data.tolerance.tolerableWrapAmount + }) returns ( + uint256 returnCollateralAmount, ISpotMarketProxy.Data memory + ) { + amount = returnCollateralAmount; + } catch Error(string memory reason) { + revert UnwrapFailed(reason); + } + + _data.collateral.transfer(_data.receiver, amount); + + emit ZapOut( + msg.sender, _data.marketId, _data.amount, amount, _data.receiver + ); + } + + /// @notice Function to wrap collateral into a synth + /// @param _data ZapData struct containing all necessary information + function wrap(ZapData calldata _data) external { + uint256 amount = _data.amount; + + _data.collateral.transferFrom(msg.sender, address(this), amount); + _data.collateral.approve(address(_data.spotMarket), amount); + + try _data.spotMarket.wrap({ + marketId: _data.marketId, + wrapAmount: amount, + minAmountReceived: _data.tolerance.tolerableWrapAmount + }) returns (uint256 amountToMint, ISpotMarketProxy.Data memory) { + amount = amountToMint; + } catch Error(string memory reason) { + revert WrapFailed(reason); + } + + IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); + synth.approve(address(_data.spotMarket), amount); + + synth.transfer(_data.receiver, amount); + + emit Wrapped( + msg.sender, _data.marketId, _data.amount, amount, _data.receiver + ); + } + + /// @notice Function to unwrap a synth back into collateral + /// @param _data ZapData struct containing all necessary information + function unwrap(ZapData calldata _data) external { + uint256 amount = _data.amount; IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); synth.approve(address(_data.spotMarket), amount); - (amount,) = _data.spotMarket.unwrap({ + try _data.spotMarket.unwrap({ marketId: _data.marketId, unwrapAmount: amount, minAmountReceived: _data.tolerance.tolerableWrapAmount - }); + }) returns ( + uint256 returnCollateralAmount, ISpotMarketProxy.Data memory + ) { + amount = returnCollateralAmount; + } catch Error(string memory reason) { + revert UnwrapFailed(reason); + } _data.collateral.transfer(_data.receiver, amount); + + emit Unwrapped( + msg.sender, _data.marketId, _data.amount, amount, _data.receiver + ); } } diff --git a/src/ZapErrors.sol b/src/ZapErrors.sol index 04a5ba0..5926e23 100644 --- a/src/ZapErrors.sol +++ b/src/ZapErrors.sol @@ -3,4 +3,18 @@ pragma solidity 0.8.26; /// @title Zap contract errors /// @author JaredBorders (jaredborders@pm.me) -contract ZapErrors {} +contract ZapErrors { + + /// @notice Thrown when a wrap operation fails + /// @param reason The reason for the failure + error WrapFailed(string reason); + + /// @notice Thrown when an unwrap operation fails + /// @param reason The reason for the failure + error UnwrapFailed(string reason); + + /// @notice Thrown when a swap operation fails + /// @param reason The reason for the failure + error SwapFailed(string reason); + +} diff --git a/src/ZapEvents.sol b/src/ZapEvents.sol index 862c923..d1725bd 100644 --- a/src/ZapEvents.sol +++ b/src/ZapEvents.sol @@ -3,4 +3,64 @@ pragma solidity 0.8.26; /// @title Zap contract events /// @author JaredBorders (jaredborders@pm.me) -contract ZapEvents {} +contract ZapEvents { + + /// @notice Emitted when a zap-in operation is performed (Collateral to + /// sUSD) + /// @param initiator Address that initiated the zap-in + /// @param marketId ID of the market involved + /// @param collateralAmount Amount of collateral input + /// @param sUSDAmount Amount of sUSD output + /// @param receiver Address that received the sUSD + event ZapIn( + address indexed initiator, + uint128 indexed marketId, + uint256 collateralAmount, + uint256 sUSDAmount, + address indexed receiver + ); + + /// @notice Emitted when a zap-out operation is performed (sUSD to + /// Collateral) + /// @param initiator Address that initiated the zap-out + /// @param marketId ID of the market involved + /// @param sUSDAmount Amount of sUSD input + /// @param collateralAmount Amount of collateral output + /// @param receiver Address that received the collateral + event ZapOut( + address indexed initiator, + uint128 indexed marketId, + uint256 sUSDAmount, + uint256 collateralAmount, + address indexed receiver + ); + + /// @notice Emitted when a wrap operation is performed + /// @param initiator Address that initiated the wrap + /// @param marketId ID of the market involved + /// @param collateralAmount Amount of collateral wrapped + /// @param synthAmount Amount of synth received + /// @param receiver Address that received the synth tokens + event Wrapped( + address indexed initiator, + uint128 indexed marketId, + uint256 collateralAmount, + uint256 synthAmount, + address indexed receiver + ); + + /// @notice Emitted when an unwrap operation is performed + /// @param initiator Address that initiated the unwrap + /// @param marketId ID of the market involved + /// @param synthAmount Amount of synth unwrapped + /// @param collateralAmount Amount of collateral received + /// @param receiver Address that received the collateral tokens + event Unwrapped( + address indexed initiator, + uint128 indexed marketId, + uint256 synthAmount, + uint256 collateralAmount, + address indexed receiver + ); + +} From d9287e59af8a602bed2dfb456c3bc70c817948cd Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 17 Sep 2024 18:04:46 +0200 Subject: [PATCH 021/129] =?UTF-8?q?=E2=9C=A8=20prettify?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 09b5152..192424a 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -10,14 +10,6 @@ import {ISpotMarketProxy} from "./interfaces/ISpotMarketProxy.sol"; /// @author JaredBorders (jaredborders@pm.me) contract Zap is ZapErrors, ZapEvents { - /** - * 2. Errors - * - Unsupported market - * - Unsupported collateral - * - Try-Catch Pattern - * 4. Tests - */ - /// @notice Constant for the USD spot market ID uint128 internal constant USD_SPOT_MARKET_ID = 0; From 4a5f6174340fdebc8004336a575ef237e0e456a1 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 17 Sep 2024 18:05:35 +0200 Subject: [PATCH 022/129] =?UTF-8?q?=E2=9C=85=20Add=20zap,=20wrap=20&=20unw?= =?UTF-8?q?rap=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Unwrap.t.sol | 98 +++++++++++++++++++ test/Wrap.t.sol | 95 +++++++++++++++++++ test/Zap.In.t.sol | 114 +++++++++++++++++++++- test/Zap.Out.t.sol | 116 ++++++++++++++++++++++- test/utils/mocks/MockSpotMarketProxy.sol | 51 +++++++--- 5 files changed, 454 insertions(+), 20 deletions(-) create mode 100644 test/Unwrap.t.sol create mode 100644 test/Wrap.t.sol diff --git a/test/Unwrap.t.sol b/test/Unwrap.t.sol new file mode 100644 index 0000000..ca11794 --- /dev/null +++ b/test/Unwrap.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +import {Zap} from "../src/Zap.sol"; +import {ZapErrors} from "../src/ZapErrors.sol"; +import {ZapEvents} from "../src/ZapEvents.sol"; + +import {MockERC20} from "./utils/mocks/MockERC20.sol"; +import {MockSpotMarketProxy} from "./utils/mocks/MockSpotMarketProxy.sol"; +import {Test} from "forge-std/Test.sol"; + +contract UnwrapTest is Test, ZapEvents { + + Zap public zap; + MockERC20 public collateral; + MockERC20 public synth; + MockSpotMarketProxy public spotMarket; + + address public constant RECEIVER = address(0x123); + address public constant REFERRER = address(0x456); + uint128 public constant MARKET_ID = 1; + uint256 public constant INITIAL_BALANCE = 1000e18; + + function setUp() public { + zap = new Zap(); + collateral = new MockERC20("Collateral", "COL", 18); + synth = new MockERC20("Synth", "SYN", 18); + spotMarket = new MockSpotMarketProxy(); + + // Set the synth address in the MockSpotMarketProxy + spotMarket.setSynthAddress(MARKET_ID, address(synth)); + + // Setup initial balances and approvals + synth.mint(address(this), INITIAL_BALANCE); + synth.approve(address(zap), type(uint256).max); + + // Mint collateral to the Zap contract instead of the SpotMarketProxy + collateral.mint(address(zap), INITIAL_BALANCE); + } + + function test_unwrap_success() public { + uint256 amount = 100e18; + uint256 expectedOutput = amount; // Assuming 1:1 ratio for simplicity + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: 0 // Not used for unwrap + }), + direction: Zap.Direction.Out, // Not used for unwrap + receiver: RECEIVER, + referrer: REFERRER // Not used for unwrap + }); + + vm.expectEmit(true, true, true, true); + emit Unwrapped( + address(this), MARKET_ID, amount, expectedOutput, RECEIVER + ); + + zap.unwrap(zapData); + + // Assert the final state + assertEq(collateral.balanceOf(RECEIVER), expectedOutput); + } + + function test_unwrap_failed() public { + uint256 amount = 100e18; + + // Setup to make unwrap fail + spotMarket.setUnwrapShouldRevert(true); + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: 0 // Not used for unwrap + }), + direction: Zap.Direction.Out, // Not used for unwrap + receiver: RECEIVER, + referrer: REFERRER // Not used for unwrap + }); + + vm.expectRevert( + abi.encodeWithSelector( + ZapErrors.UnwrapFailed.selector, "Unwrap failed" + ) + ); + zap.unwrap(zapData); + } + +} diff --git a/test/Wrap.t.sol b/test/Wrap.t.sol new file mode 100644 index 0000000..f105c58 --- /dev/null +++ b/test/Wrap.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +import {Zap} from "../src/Zap.sol"; +import {ZapErrors} from "../src/ZapErrors.sol"; +import {ZapEvents} from "../src/ZapEvents.sol"; + +import {MockERC20} from "./utils/mocks/MockERC20.sol"; +import {MockSpotMarketProxy} from "./utils/mocks/MockSpotMarketProxy.sol"; +import {Test} from "forge-std/Test.sol"; + +contract WrapTest is Test, ZapEvents { + + Zap public zap; + MockERC20 public collateral; + MockERC20 public synth; + MockSpotMarketProxy public spotMarket; + + address public constant RECEIVER = address(0x123); + address public constant REFERRER = address(0x456); + uint128 public constant MARKET_ID = 1; + uint256 public constant INITIAL_BALANCE = 1000e18; + + function setUp() public { + zap = new Zap(); + collateral = new MockERC20("Collateral", "COL", 18); + synth = new MockERC20("Synth", "SYN", 18); + spotMarket = new MockSpotMarketProxy(); + + // Set the synth address in the MockSpotMarketProxy + spotMarket.setSynthAddress(MARKET_ID, address(synth)); + + // Setup initial balances and approvals + collateral.mint(address(this), INITIAL_BALANCE); + collateral.approve(address(zap), type(uint256).max); + + // Mint synth tokens to the MockSpotMarketProxy for it to transfer + // during 'wrap' + synth.mint(address(zap), INITIAL_BALANCE); + } + + function test_wrap_success() public { + uint256 amount = 100e18; + uint256 expectedOutput = amount; // Assuming 1:1 ratio for simplicity + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: 0 // Not used for wrap + }), + direction: Zap.Direction.In, // Not used for wrap + receiver: RECEIVER, + referrer: REFERRER // Not used for wrap + }); + + vm.expectEmit(true, true, true, true); + emit Wrapped(address(this), MARKET_ID, amount, expectedOutput, RECEIVER); + + zap.wrap(zapData); + + // Assert the final state + assertEq(synth.balanceOf(RECEIVER), expectedOutput); + } + + function test_wrap_failed() public { + uint256 amount = 100e18; + + // Setup to make wrap fail + spotMarket.setWrapShouldRevert(true); + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: 0 // Not used for wrap + }), + direction: Zap.Direction.In, // Not used for wrap + receiver: RECEIVER, + referrer: REFERRER // Not used for wrap + }); + + vm.expectRevert( + abi.encodeWithSelector(ZapErrors.WrapFailed.selector, "Wrap failed") + ); + zap.wrap(zapData); + } + +} diff --git a/test/Zap.In.t.sol b/test/Zap.In.t.sol index 366afa9..b91be96 100644 --- a/test/Zap.In.t.sol +++ b/test/Zap.In.t.sol @@ -2,18 +2,124 @@ pragma solidity 0.8.26; import {Zap} from "../src/Zap.sol"; +import {ZapErrors} from "../src/ZapErrors.sol"; +import {ZapEvents} from "../src/ZapEvents.sol"; + +import {MockERC20} from "./utils/mocks/MockERC20.sol"; +import {MockSpotMarketProxy} from "./utils/mocks/MockSpotMarketProxy.sol"; import {Test} from "forge-std/Test.sol"; -contract ZapInTest is Test { +contract ZapInTest is Test, ZapEvents { + + Zap public zap; + MockERC20 public collateral; + MockERC20 public synth; + MockERC20 public sUSD; + MockSpotMarketProxy public spotMarket; - Zap zap; + address public constant RECEIVER = address(0x123); + address public constant REFERRER = address(0x456); + uint128 public constant MARKET_ID = 1; + uint128 public constant USD_SPOT_MARKET_ID = 0; + uint256 public constant INITIAL_BALANCE = 1000e18; function setUp() public { zap = new Zap(); + collateral = new MockERC20("Collateral", "COL", 18); + synth = new MockERC20("Synth", "SYN", 18); + sUSD = new MockERC20("sUSD", "sUSD", 18); + spotMarket = new MockSpotMarketProxy(); + + // Set the synth address in the MockSpotMarketProxy + spotMarket.setSynthAddress(MARKET_ID, address(synth)); + spotMarket.setSynthAddress(USD_SPOT_MARKET_ID, address(sUSD)); + + // Setup initial balances and approvals + collateral.mint(address(this), INITIAL_BALANCE); + collateral.approve(address(zap), type(uint256).max); + + synth.mint(address(zap), INITIAL_BALANCE); + sUSD.mint(address(zap), INITIAL_BALANCE); } - function test_zap_in() public { - assertTrue(true); + function test_zapIn_success() public { + uint256 amount = 100e18; + uint256 expectedOutput = amount; + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: expectedOutput * 95 / 100 + }), + direction: Zap.Direction.In, + receiver: RECEIVER, + referrer: REFERRER + }); + + vm.expectEmit(true, true, true, true); + emit ZapIn(address(this), MARKET_ID, amount, expectedOutput, RECEIVER); + + zap.zap(zapData); + + // Assert the final state + assertEq(collateral.balanceOf(address(this)), INITIAL_BALANCE - amount); + assertEq(sUSD.balanceOf(RECEIVER), expectedOutput); + } + + function test_zapIn_wrapFailed() public { + uint256 amount = 100e18; + + // Setup to make wrap fail + spotMarket.setWrapShouldRevert(true); + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: amount * 95 / 100 + }), + direction: Zap.Direction.In, + receiver: RECEIVER, + referrer: REFERRER + }); + + vm.expectRevert( + abi.encodeWithSelector(ZapErrors.WrapFailed.selector, "Wrap failed") + ); + zap.zap(zapData); + } + + function test_zapIn_swapFailed() public { + uint256 amount = 100e18; + + // Setup to make sell fail + spotMarket.setSellShouldRevert(true); + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: amount * 95 / 100 + }), + direction: Zap.Direction.In, + receiver: RECEIVER, + referrer: REFERRER + }); + + vm.expectRevert( + abi.encodeWithSelector(ZapErrors.SwapFailed.selector, "Sell failed") + ); + zap.zap(zapData); } } diff --git a/test/Zap.Out.t.sol b/test/Zap.Out.t.sol index 980607f..89c30b2 100644 --- a/test/Zap.Out.t.sol +++ b/test/Zap.Out.t.sol @@ -2,18 +2,126 @@ pragma solidity 0.8.26; import {Zap} from "../src/Zap.sol"; +import {ZapErrors} from "../src/ZapErrors.sol"; +import {ZapEvents} from "../src/ZapEvents.sol"; + +import {MockERC20} from "./utils/mocks/MockERC20.sol"; +import {MockSpotMarketProxy} from "./utils/mocks/MockSpotMarketProxy.sol"; import {Test} from "forge-std/Test.sol"; -contract ZapOutTest is Test { +contract ZapOutTest is Test, ZapEvents { + + Zap public zap; + MockERC20 public collateral; + MockERC20 public synth; + MockERC20 public sUSD; + MockSpotMarketProxy public spotMarket; - Zap zap; + address public constant RECEIVER = address(0x123); + address public constant REFERRER = address(0x456); + uint128 public constant MARKET_ID = 1; + uint128 public constant USD_SPOT_MARKET_ID = 0; + uint256 public constant INITIAL_BALANCE = 1000e18; function setUp() public { zap = new Zap(); + collateral = new MockERC20("Collateral", "COL", 18); + synth = new MockERC20("Synth", "SYN", 18); + sUSD = new MockERC20("sUSD", "sUSD", 18); + spotMarket = new MockSpotMarketProxy(); + + // Set the synth address in the MockSpotMarketProxy + spotMarket.setSynthAddress(MARKET_ID, address(synth)); + spotMarket.setSynthAddress(USD_SPOT_MARKET_ID, address(sUSD)); + + // Setup initial balances and approvals + sUSD.mint(address(this), INITIAL_BALANCE); + sUSD.approve(address(zap), type(uint256).max); + + synth.mint(address(zap), INITIAL_BALANCE); + collateral.mint(address(zap), INITIAL_BALANCE); } - function test_zap_out() public { - assertTrue(true); + function test_zapOut_success() public { + uint256 amount = 100e18; + uint256 expectedOutput = amount; // Assuming 1:1 ratio for simplicity + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: expectedOutput * 95 / 100, + tolerableSwapAmount: amount * 95 / 100 + }), + direction: Zap.Direction.Out, + receiver: RECEIVER, + referrer: REFERRER + }); + + vm.expectEmit(true, true, true, true); + emit ZapOut(address(this), MARKET_ID, amount, expectedOutput, RECEIVER); + + zap.zap(zapData); + + // Assert the final state + assertEq(sUSD.balanceOf(address(this)), INITIAL_BALANCE - amount); + assertEq(collateral.balanceOf(RECEIVER), expectedOutput); + } + + function test_zapOut_swapFailed() public { + uint256 amount = 100e18; + + // Setup to make buy fail + spotMarket.setBuyShouldRevert(true); + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: amount * 95 / 100 + }), + direction: Zap.Direction.Out, + receiver: RECEIVER, + referrer: REFERRER + }); + + vm.expectRevert( + abi.encodeWithSelector(ZapErrors.SwapFailed.selector, "Buy failed") + ); + zap.zap(zapData); + } + + function test_zapOut_unwrapFailed() public { + uint256 amount = 100e18; + + // Setup to make unwrap fail + spotMarket.setUnwrapShouldRevert(true); + + Zap.ZapData memory zapData = Zap.ZapData({ + spotMarket: spotMarket, + collateral: collateral, + marketId: MARKET_ID, + amount: amount, + tolerance: Zap.Tolerance({ + tolerableWrapAmount: amount * 95 / 100, + tolerableSwapAmount: amount * 95 / 100 + }), + direction: Zap.Direction.Out, + receiver: RECEIVER, + referrer: REFERRER + }); + + vm.expectRevert( + abi.encodeWithSelector( + ZapErrors.UnwrapFailed.selector, "Unwrap failed" + ) + ); + zap.zap(zapData); } } diff --git a/test/utils/mocks/MockSpotMarketProxy.sol b/test/utils/mocks/MockSpotMarketProxy.sol index cfd5338..003fc39 100644 --- a/test/utils/mocks/MockSpotMarketProxy.sol +++ b/test/utils/mocks/MockSpotMarketProxy.sol @@ -7,9 +7,34 @@ import {ISpotMarketProxy} from "./../../../src/interfaces/ISpotMarketProxy.sol"; /// @author JaredBorders (jaredborders@pm.me) contract MockSpotMarketProxy is ISpotMarketProxy { - function name( - uint128 /* marketId */ - ) + bool public wrapShouldRevert; + bool public unwrapShouldRevert; + bool public sellShouldRevert; + bool public buyShouldRevert; + + mapping(uint128 => address) public synthAddresses; + + function setWrapShouldRevert(bool _shouldRevert) external { + wrapShouldRevert = _shouldRevert; + } + + function setUnwrapShouldRevert(bool _shouldRevert) external { + unwrapShouldRevert = _shouldRevert; + } + + function setBuyShouldRevert(bool _shouldRevert) external { + buyShouldRevert = _shouldRevert; + } + + function setSellShouldRevert(bool _shouldRevert) external { + sellShouldRevert = _shouldRevert; + } + + function setSynthAddress(uint128 marketId, address synthAddress) external { + synthAddresses[marketId] = synthAddress; + } + + function name(uint128 /* marketId */ ) external pure override @@ -18,15 +43,13 @@ contract MockSpotMarketProxy is ISpotMarketProxy { return "MockName"; } - function getSynth( - uint128 /* marketId */ - ) + function getSynth(uint128 marketId) external - pure + view override returns (address) { - return address(0x1); + return synthAddresses[marketId]; } function wrap( @@ -35,10 +58,11 @@ contract MockSpotMarketProxy is ISpotMarketProxy { uint256 /* minAmountReceived */ ) external - pure + view override returns (uint256 amountToMint, Data memory fees) { + require(!wrapShouldRevert, "Wrap failed"); return (wrapAmount, Data(0, 0, 0, 0)); } @@ -48,10 +72,11 @@ contract MockSpotMarketProxy is ISpotMarketProxy { uint256 /* minAmountReceived */ ) external - pure + view override returns (uint256 returnCollateralAmount, Data memory fees) { + require(!unwrapShouldRevert, "Unwrap failed"); return (unwrapAmount, Data(0, 0, 0, 0)); } @@ -62,10 +87,11 @@ contract MockSpotMarketProxy is ISpotMarketProxy { address /* referrer */ ) external - pure + view override returns (uint256 synthAmount, Data memory fees) { + require(!buyShouldRevert, "Buy failed"); return (usdAmount, Data(0, 0, 0, 0)); } @@ -76,10 +102,11 @@ contract MockSpotMarketProxy is ISpotMarketProxy { address /* referrer */ ) external - pure + view override returns (uint256 usdAmountReceived, Data memory fees) { + require(!sellShouldRevert, "Sell failed"); return (synthAmount, Data(0, 0, 0, 0)); } From ff7476ad63d42d5563243c5ff1059f1b88bd9938 Mon Sep 17 00:00:00 2001 From: jaredborders <44350516+JaredBorders@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:41:45 -0400 Subject: [PATCH 023/129] =?UTF-8?q?=F0=9F=91=B7=20add=20arbitrum=20&=20bas?= =?UTF-8?q?e=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-example | 13 +- README.md | 23 + foundry.toml | 2 +- script/Deploy.s.sol | 79 +- script/utils/Parameters.sol | 40 + .../utils/parameters/ArbitrumParameters.sol | 4 - .../parameters/ArbitrumSepoliaParameters.sol | 4 - script/utils/parameters/BaseParameters.sol | 4 - .../parameters/BaseSepoliaParameters.sol | 4 - .../utils/parameters/OptimismParameters.sol | 4 - .../parameters/OptimismSepoliaParameters.sol | 4 - src/Errors.sol | 24 + src/Events.sol | 62 + src/Types.sol | 56 + src/Zap.sol | 612 +++++-- src/ZapErrors.sol | 20 - src/ZapEvents.sol | 66 - src/interfaces/IAave.sol | 1533 +++++++++++++++++ src/interfaces/IERC20.sol | 4 +- src/interfaces/ISpotMarketProxy.sol | 133 -- src/interfaces/ISynthetix.sol | 1123 ++++++++++++ src/interfaces/IUniswap.sol | 21 + test/Unwrap.t.sol | 98 -- test/Wrap.t.sol | 95 - test/Zap.In.t.sol | 125 -- test/Zap.Out.t.sol | 127 -- test/Zap.t.sol | 352 ++++ test/utils/errors/SynthetixV3Errors.sol | 4 +- test/utils/mocks/MockERC20.sol | 87 - test/utils/mocks/MockSpotMarketProxy.sol | 113 -- 30 files changed, 3701 insertions(+), 1135 deletions(-) create mode 100644 script/utils/Parameters.sol delete mode 100644 script/utils/parameters/ArbitrumParameters.sol delete mode 100644 script/utils/parameters/ArbitrumSepoliaParameters.sol delete mode 100644 script/utils/parameters/BaseParameters.sol delete mode 100644 script/utils/parameters/BaseSepoliaParameters.sol delete mode 100644 script/utils/parameters/OptimismParameters.sol delete mode 100644 script/utils/parameters/OptimismSepoliaParameters.sol create mode 100644 src/Errors.sol create mode 100644 src/Events.sol create mode 100644 src/Types.sol delete mode 100644 src/ZapErrors.sol delete mode 100644 src/ZapEvents.sol create mode 100644 src/interfaces/IAave.sol delete mode 100644 src/interfaces/ISpotMarketProxy.sol create mode 100644 src/interfaces/ISynthetix.sol create mode 100644 src/interfaces/IUniswap.sol delete mode 100644 test/Unwrap.t.sol delete mode 100644 test/Wrap.t.sol delete mode 100644 test/Zap.In.t.sol delete mode 100644 test/Zap.Out.t.sol create mode 100644 test/Zap.t.sol delete mode 100644 test/utils/mocks/MockERC20.sol delete mode 100644 test/utils/mocks/MockSpotMarketProxy.sol diff --git a/.env-example b/.env-example index f505587..c8a9394 100644 --- a/.env-example +++ b/.env-example @@ -1,10 +1,5 @@ -MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/KEY -OPTIMISM_RPC_URL=https://opt-mainnet.g.alchemy.com/v2/KEY -BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/KEY -GOERLI_RPC_URL=https://eth-goerli.g.alchemy.com/v2/KEY -OPTIMISM_GOERLI_RPC_URL=https://opt-goerli.g.alchemy.com/v2/KEY -BASE_GOERLI_RPC_URL=https://base-goerli.g.alchemy.com/v2/KEY +BASE_RPC=https://base-mainnet.g.alchemy.com/v2/KEY +ARBITRUM_RPC=https://arbitrum-mainnet.infura.io/v3/KEY PRIVATE_KEY=KEY -ETHERSCAN_API_KEY=KEY -OPTIMISM_ETHERSCAN_API_KEY=KEY -BASESCAN_API_KEY=KEY \ No newline at end of file +BASESCAN_API_KEY=KEY +ARBISCAN_API_KEY=KEY \ No newline at end of file diff --git a/README.md b/README.md index cbbd0df..e988f58 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,29 @@ [license]: https://opensource.org/license/GPL-3.0/ [license-badge]: https://img.shields.io/badge/GitHub-GPL--3.0-informational +## Aave Flashloans + +Execution Flow +For developers, a helpful mental model to consider when developing your solution: +Your contract calls the Pool contract, requesting a Flash Loan of a certain amount(s) of reserve(s) using flashLoanSimple() or flashLoan(). +After some sanity checks, the Pool transfers the requested amounts of the reserves to your contract, then calls executeOperation() on receiver contract . +Your contract, now holding the flash loaned amount(s), executes any arbitrary operation in its code. +If you are performing a flashLoanSimple, then when your code has finished, you approve Pool for flash loaned amount + fee. +If you are performing flashLoan, then for all the reserves either depending on interestRateMode passed for the asset, either the Pool must be approved for flash loaned amount + fee or must or sufficient collateral or credit delegation should be available to open debt position. +If the amount owing is not available (due to a lack of balance or approval or insufficient collateral for debt), then the transaction is reverted. +All of the above happens in 1 transaction (hence in a single ethereum block). + +The flash loan fee is initialized at deployment to 0.05% and can be updated via Governance Vote. Use FLASHLOAN_PREMIUM_TOTAL to get current value. +Flashloan fee can be shared by the LPs (liquidity providers) and the protocol treasury. The FLASHLOAN_PREMIUM_TOTAL represents the total fee paid by the borrowers of which: +Fee to LP: FLASHLOAN_PREMIUM_TOTAL - FLASHLOAN_PREMIUM_TO_PROTOCOL +Fee to Protocol: FLASHLOAN_PREMIUM_TO_PROTOCOL + +The pool.sol contract is the main user facing contract of the protocol. It exposes the liquidity management methods that can be invoked using either Solidity or Web3 libraries. + +flashLoanSimple +function flashLoanSimple( address receiverAddress, address asset, uint256 amount, bytes calldata params, uint16 referralCode) +Allows users to access liquidity of one reserve or one transaction as long as the amount taken plus fee is returned. + ## Tests 1. Follow the [Foundry guide to working on an existing project](https://book.getfoundry.sh/projects/working-on-an-existing-project.html) diff --git a/foundry.toml b/foundry.toml index 76caaa8..8db7c35 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,7 +3,7 @@ src = 'src' test = 'test/' out = 'out' libs = ['lib'] -solc_version = "0.8.26" +solc_version = "0.8.27" optimizer = true optimizer_runs = 1_000_000 diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 6fde29e..1d6bf96 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; import {Script} from "../lib/forge-std/src/Script.sol"; import {Zap} from "../src/Zap.sol"; @@ -9,7 +9,8 @@ import {Zap} from "../src/Zap.sol"; contract Setup is Script { function deploySystem() public returns (address zap) { - zap = address(new Zap()); + /// @custom:todo + // zap = address(new Zap()); } } @@ -31,60 +32,6 @@ contract DeployBase is Setup { } -/// @dev steps to deploy and verify on Base Sepolia: -/// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployBaseSepolia --rpc-url -/// $BASE_SEPOLIA_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast -/// --verify -vvvv` -contract DeployBaseSepolia is Setup { - - function run() public { - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(privateKey); - - Setup.deploySystem(); - - vm.stopBroadcast(); - } - -} - -/// @dev steps to deploy and verify on Optimism: -/// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployOptimism --rpc-url -/// $OPTIMISM_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY -/// --broadcast --verify -vvvv` -contract DeployOptimism is Setup { - - function run() public { - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(privateKey); - - Setup.deploySystem(); - - vm.stopBroadcast(); - } - -} - -/// @dev steps to deploy and verify on Optimism Sepolia: -/// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployOptimismSepolia --rpc-url -/// $OPTIMISM_SEPOLIA_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY -/// --broadcast --verify -vvvv` -contract DeployOptimismSepolia is Setup { - - function run() public { - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(privateKey); - - Setup.deploySystem(); - - vm.stopBroadcast(); - } - -} - /// @dev steps to deploy and verify on Arbitrum: /// (1) load the variables in the .env file via `source .env` /// (2) run `forge script script/Deploy.s.sol:DeployArbitrum --rpc-url @@ -102,21 +49,3 @@ contract DeployArbitrum is Setup { } } - -/// @dev steps to deploy and verify on Arbitrum Sepolia: -/// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployArbitrumSepolia --rpc-url -/// $ARBITRUM_SEPOLIA_RPC_URL --etherscan-api-key $ARBITRUM_SEPOLIA_RPC_URL -/// --broadcast --verify -vvvv` -contract DeployArbitrumSepolia is Setup { - - function run() public { - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(privateKey); - - Setup.deploySystem(); - - vm.stopBroadcast(); - } - -} diff --git a/script/utils/Parameters.sol b/script/utils/Parameters.sol new file mode 100644 index 0000000..81590be --- /dev/null +++ b/script/utils/Parameters.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +contract Base { + + /// @custom:synthetix + address BASE_USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + address BASE_USDX = 0x09d51516F38980035153a554c26Df3C6f51a23C3; + address BASE_SPOT_MARKET = 0x18141523403e2595D31b22604AcB8Fc06a4CaA61; + address BASE_PERPS_MARKET = 0x0A2AF931eFFd34b81ebcc57E3d3c9B1E1dE1C9Ce; + address BASE_CORE = 0x32C222A9A159782aFD7529c87FA34b96CA72C696; + address BASE_REFERRER = address(0); + uint128 BASE_SUSDC_SPOT_MARKET_ID = 1; + + /// @custom:aave + address BASE_AAVE_POOL = 0xA238Dd80C259a72e81d7e4664a9801593F98d1c5; + + /// @custom:uniswap + address BASE_UNISWAP = 0x2626664c2603336E57B271c5C0b26F421741e481; + +} + +contract Arbitrum { + + /// @custom:synthetix + address ARBITRUM_USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; + address ARBITRUM_USDX = 0xb2F30A7C980f052f02563fb518dcc39e6bf38175; + address ARBITRUM_SPOT_MARKET = 0xa65538A6B9A8442854dEcB6E3F85782C60757D60; + address ARBITRUM_PERPS_MARKET = 0xd762960c31210Cf1bDf75b06A5192d395EEDC659; + address ARBITRUM_CORE = 0xffffffaEff0B96Ea8e4f94b2253f31abdD875847; + address ARBITRUM_REFERRER = address(0); + uint128 ARBITRUM_SUSDC_SPOT_MARKET_ID = 2; + + /// @custom:aave + address ARBITRUM_AAVE_POOL = 0x794a61358D6845594F94dc1DB02A252b5b4814aD; + + /// @custom:uniswap + address ARBITRUM_UNISWAP = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; + +} diff --git a/script/utils/parameters/ArbitrumParameters.sol b/script/utils/parameters/ArbitrumParameters.sol deleted file mode 100644 index 5d42f46..0000000 --- a/script/utils/parameters/ArbitrumParameters.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -contract ArbitrumParameters {} diff --git a/script/utils/parameters/ArbitrumSepoliaParameters.sol b/script/utils/parameters/ArbitrumSepoliaParameters.sol deleted file mode 100644 index 1c3d4e2..0000000 --- a/script/utils/parameters/ArbitrumSepoliaParameters.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -contract ArbitrumSepoliaParameters {} diff --git a/script/utils/parameters/BaseParameters.sol b/script/utils/parameters/BaseParameters.sol deleted file mode 100644 index c00bf40..0000000 --- a/script/utils/parameters/BaseParameters.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -contract BaseParameters {} diff --git a/script/utils/parameters/BaseSepoliaParameters.sol b/script/utils/parameters/BaseSepoliaParameters.sol deleted file mode 100644 index 426d4f9..0000000 --- a/script/utils/parameters/BaseSepoliaParameters.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -contract BaseSepoliaParameters {} diff --git a/script/utils/parameters/OptimismParameters.sol b/script/utils/parameters/OptimismParameters.sol deleted file mode 100644 index 198433b..0000000 --- a/script/utils/parameters/OptimismParameters.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -contract OptimismParameters {} diff --git a/script/utils/parameters/OptimismSepoliaParameters.sol b/script/utils/parameters/OptimismSepoliaParameters.sol deleted file mode 100644 index f115de3..0000000 --- a/script/utils/parameters/OptimismSepoliaParameters.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -contract OptimismSepoliaParameters {} diff --git a/src/Errors.sol b/src/Errors.sol new file mode 100644 index 0000000..c7d6d8a --- /dev/null +++ b/src/Errors.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +/// @title zap errors +/// @author @jaredborders +contract Errors { + + /// @notice thrown when a wrap operation fails + /// @param reason string for the failure + error WrapFailed(string reason); + + /// @notice thrown when an unwrap operation fails + /// @param reason string for the failure + error UnwrapFailed(string reason); + + /// @notice thrown when a swap operation fails + /// @param reason string for the failure + error SwapFailed(string reason); + + /// @notice thrown when an address who is not permitted + /// to modify an account's collateral attempts to + error NotPermittedToModifyCollateral(); + +} diff --git a/src/Events.sol b/src/Events.sol new file mode 100644 index 0000000..6c365b0 --- /dev/null +++ b/src/Events.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +/// @title zap events +/// @author @jaredborders +contract Events { +// /// @notice emitted when a zap-in operation is performed +// /// @param initiator address that initiated the zap-in +// /// @param marketId identifier of the spot market +// /// @param collateralAmount exchanged for USD +// /// @param usdAmount amount of USD received +// /// @param receiver address that received the USD +// event ZapIn( +// address indexed initiator, +// uint128 indexed marketId, +// uint256 collateralAmount, +// uint256 usdAmount, +// address indexed receiver +// ); + +// /// @notice emitted when a zap-out operation is performed +// /// @param initiator address that initiated the zap-out +// /// @param marketId identifier of the spot market +// /// @param usdAmount exchanged for collateral +// /// @param collateralAmount amount of collateral received +// /// @param receiver address that received the collateral +// event ZapOut( +// address indexed initiator, +// uint128 indexed marketId, +// uint256 usdAmount, +// uint256 collateralAmount, +// address indexed receiver +// ); + +// /// @notice emitted when a wrap operation is performed +// /// @param initiator Address that initiated the wrap +// /// @param marketId ID of the market involved +// /// @param collateralAmount Amount of collateral wrapped +// /// @param synthAmount Amount of synth received +// /// @param receiver Address that received the synth tokens +// event Wrapped( +// address indexed initiator, +// uint128 indexed marketId, +// uint256 collateralAmount, +// uint256 synthAmount, +// address indexed receiver +// ); + +// /// @notice emitted when an unwrap operation is performed +// /// @param initiator Address that initiated the unwrap +// /// @param marketId ID of the market involved +// /// @param synthAmount Amount of synth unwrapped +// /// @param collateralAmount Amount of collateral received +// /// @param receiver Address that received the collateral tokens +// event Unwrapped( +// address indexed initiator, +// uint128 indexed marketId, +// uint256 synthAmount, +// uint256 collateralAmount, +// address indexed receiver +// ); +} diff --git a/src/Types.sol b/src/Types.sol new file mode 100644 index 0000000..e4f758b --- /dev/null +++ b/src/Types.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +contract Types { + + /// @notice enumerated type for the direction of a zap operation + /// @custom:IN collateral -> synthetix stablecoin + /// @custom:OUT synthetix collateral -> collateral + enum Direction { + IN, + OUT + } + + /// @notice tolerance levels for wrap and swap operations + /// @param tolerableWrapAmount minimum acceptable amount for wrap operation + /// @param tolerableSwapAmount minimum acceptable amount for swap operation + struct Tolerance { + uint256 tolerableWrapAmount; + uint256 tolerableSwapAmount; + } + + /// @notice structure of data required for any zap operation + /// @param spotMarket address of the spot market proxy + /// @param collateral address of the collateral token + /// @param marketId identifier of the spot market + /// @param amount of tokens to zap + /// @param tolerance levels for wrap and swap operations + /// @param direction of the zap operation + /// @param receiver address to receive the output tokens + /// @param referrer address of the referrer + struct ZapData { + address spotMarket; + address collateral; + uint128 marketId; + uint256 amount; + Tolerance tolerance; + Direction direction; + address receiver; + address referrer; + } + + /// @notice structure of data required for a flash loan operation + /// @param asset address of the asset to be borrowed + /// @param amount of the asset to be borrowed + /// @param premium fee to be paid for the flash loan + /// @param initiator address of the flash loan operation + /// @param params parameters needed for initiating the loan + struct FlashData { + address asset; + uint256 amount; + uint256 premium; + address initiator; + bytes params; + } + +} diff --git a/src/Zap.sol b/src/Zap.sol index 192424a..95f62f0 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -1,198 +1,494 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; -import {ZapErrors} from "./ZapErrors.sol"; -import {ZapEvents} from "./ZapEvents.sol"; +import {Errors} from "./Errors.sol"; +import {Events} from "./Events.sol"; +import {Types} from "./Types.sol"; +import {IPool} from "./interfaces/IAave.sol"; import {IERC20} from "./interfaces/IERC20.sol"; -import {ISpotMarketProxy} from "./interfaces/ISpotMarketProxy.sol"; - -/// @title Zap contract for zapping collateral in/out of Synthetix v3 -/// @author JaredBorders (jaredborders@pm.me) -contract Zap is ZapErrors, ZapEvents { - - /// @notice Constant for the USD spot market ID - uint128 internal constant USD_SPOT_MARKET_ID = 0; - - /// @notice Enum to specify the direction of the zap operation - /// @dev In: Collateral to sUSD, Out: sUSD to Collateral - enum Direction { - In, - Out - } - - /// @notice Struct to define tolerance levels for wrap and swap operations - /// @param tolerableWrapAmount Minimum acceptable amount for wrap/unwrap - /// operations - /// @param tolerableSwapAmount Minimum acceptable amount for swap operations - struct Tolerance { - uint256 tolerableWrapAmount; - uint256 tolerableSwapAmount; - } - - /// @notice Struct containing all necessary data for a zap operation - /// @param spotMarket Address of the spot market proxy contract - /// @param collateral Address of the collateral token - /// @param marketId ID of the target market - /// @param amount Amount of tokens to zap - /// @param tolerance Tolerance struct for minimum acceptable amounts - /// @param direction Direction of the zap (In or Out) - /// @param receiver Address to receive the output tokens - /// @param referrer Address of the referrer - struct ZapData { - ISpotMarketProxy spotMarket; - IERC20 collateral; - uint128 marketId; - uint256 amount; - Tolerance tolerance; - Direction direction; - address receiver; - address referrer; - } - - /// @notice Main function to perform a zap operation - /// @param _data ZapData struct containing all necessary information - function zap(ZapData calldata _data) external { - _data.direction == Direction.In ? _zapIn(_data) : _zapOut(_data); - } - - /// @notice Internal function to perform a zap-in operation (Collateral to - /// sUSD) - /// @param _data ZapData struct containing all necessary information - function _zapIn(ZapData calldata _data) private { - uint256 amount = _data.amount; - - _data.collateral.transferFrom(msg.sender, address(this), amount); - _data.collateral.approve(address(_data.spotMarket), amount); - - try _data.spotMarket.wrap({ - marketId: _data.marketId, - wrapAmount: amount, - minAmountReceived: _data.tolerance.tolerableWrapAmount - }) returns (uint256 amountToMint, ISpotMarketProxy.Data memory) { - amount = amountToMint; +import {ICore, IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; +import {IUniswap} from "./interfaces/IUniswap.sol"; + +contract Zap is Types, Errors, Events { + + /// @custom:synthetix + address public immutable USDC; + address public immutable USDX; + address public immutable SPOT_MARKET; + address public immutable PERPS_MARKET; + address public immutable CORE; + address public immutable REFERRER; + uint128 public immutable SUSDC_SPOT_ID; + uint128 public immutable PREFFERED_POOL_ID; + bytes32 public immutable MODIFY_PERMISSION; + + /// @custom:aave + address public immutable AAVE; + uint16 public immutable REFERRAL_CODE; + + /// @custom:uniswap + address public immutable UNISWAP; + address public immutable FACTORY; + uint24 public immutable FEE_TIER; + + constructor( + address _usdc, + address _usdx, + address _spotMarket, + address _perpsMarket, + address _core, + address _referrer, + uint128 _susdcSpotId, + address _aave, + address _uniswap + ) { + /// @custom:synthetix address assignment + USDC = _usdc; + USDX = _usdx; + SPOT_MARKET = _spotMarket; + PERPS_MARKET = _perpsMarket; + CORE = _core; + REFERRER = _referrer; + SUSDC_SPOT_ID = _susdcSpotId; + PREFFERED_POOL_ID = ICore(CORE).getPreferredPool(); + MODIFY_PERMISSION = "PERPS_MODIFY_COLLATERAL"; + + /// @custom:aave + AAVE = _aave; + REFERRAL_CODE = 0; + + /// @custom:uniswap + UNISWAP = _uniswap; + FACTORY = 0x33128a8fC17869897dcE68Ed026d694621f6FDfD; + FEE_TIER = 3000; + } + + function zapIn( + uint256 _amount, + uint256 _tolerance, + address _receiver + ) + external + returns (uint256 zapped) + { + _pull(USDC, msg.sender, _amount); + zapped = _zapIn(_amount, _tolerance); + _push(USDX, _receiver, zapped); + } + + function _zapIn( + uint256 _amount, + uint256 _tolerance + ) + internal + returns (uint256 zapped) + { + zapped = _wrap(USDC, SUSDC_SPOT_ID, _amount, _tolerance); + zapped = _sell(SUSDC_SPOT_ID, zapped, _tolerance); + } + + function zapOut( + uint256 _amount, + uint256 _tolerance, + address _receiver + ) + external + returns (uint256 zapped) + { + _pull(USDX, msg.sender, _amount); + zapped = _zapOut(_amount, _tolerance); + _push(USDC, _receiver, zapped); + } + + function _zapOut( + uint256 _amount, + uint256 _tolerance + ) + internal + returns (uint256 zapped) + { + (zapped,) = _buy(SUSDC_SPOT_ID, _amount, _tolerance); + zapped = _unwrap(SUSDC_SPOT_ID, zapped, _tolerance); + } + + function wrap( + address _token, + uint128 _synthId, + uint256 _amount, + uint256 _tolerance, + address _receiver + ) + external + returns (uint256 wrapped) + { + _pull(_token, msg.sender, _amount); + wrapped = _wrap(_token, _synthId, _amount, _tolerance); + address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); + _push(synth, _receiver, wrapped); + } + + function _wrap( + address _token, + uint128 _synthId, + uint256 _amount, + uint256 _tolerance + ) + internal + returns (uint256 wrapped) + { + IERC20(_token).approve(SPOT_MARKET, _amount); + + try ISpotMarket(SPOT_MARKET).wrap({ + marketId: _synthId, + wrapAmount: _amount, + minAmountReceived: _tolerance + }) returns (uint256 amount, ISpotMarket.Data memory) { + wrapped = amount; } catch Error(string memory reason) { revert WrapFailed(reason); } + } - IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); - synth.approve(address(_data.spotMarket), amount); + function unwrap( + address _token, + uint128 _synthId, + uint256 _amount, + uint256 _tolerance, + address _receiver + ) + external + returns (uint256 unwrapped) + { + address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); + _pull(synth, msg.sender, _amount); + unwrapped = _unwrap(_synthId, _amount, _tolerance); + _push(_token, _receiver, unwrapped); + } - try _data.spotMarket.sell({ - marketId: _data.marketId, - synthAmount: amount, - minUsdAmount: _data.tolerance.tolerableSwapAmount, - referrer: _data.referrer - }) returns (uint256 usdAmountReceived, ISpotMarketProxy.Data memory) { - amount = usdAmountReceived; + function _unwrap( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance + ) + private + returns (uint256 unwrapped) + { + address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); + IERC20(synth).approve(SPOT_MARKET, _amount); + try ISpotMarket(SPOT_MARKET).unwrap({ + marketId: _synthId, + unwrapAmount: _amount, + minAmountReceived: _tolerance + }) returns (uint256 amount, ISpotMarket.Data memory) { + unwrapped = amount; } catch Error(string memory reason) { - revert SwapFailed(reason); + revert UnwrapFailed(reason); } - - IERC20 sUSD = IERC20(_data.spotMarket.getSynth(USD_SPOT_MARKET_ID)); - sUSD.transfer(_data.receiver, amount); - - emit ZapIn( - msg.sender, _data.marketId, _data.amount, amount, _data.receiver - ); } - /// @notice Internal function to perform a zap-out operation (sUSD to - /// Collateral) - /// @param _data ZapData struct containing all necessary information - function _zapOut(ZapData calldata _data) private { - uint256 amount = _data.amount; - - IERC20 sUSD = IERC20(_data.spotMarket.getSynth(USD_SPOT_MARKET_ID)); - sUSD.transferFrom(msg.sender, address(this), amount); - - sUSD.approve(address(_data.spotMarket), amount); + function buy( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance, + address _receiver + ) + external + returns (uint256 received, address synth) + { + _pull(USDX, msg.sender, _amount); + (received, synth) = _buy(_synthId, _amount, _tolerance); + _push(synth, _receiver, received); + } - try _data.spotMarket.buy({ - marketId: _data.marketId, - usdAmount: amount, - minAmountReceived: _data.tolerance.tolerableSwapAmount, - referrer: _data.referrer - }) returns (uint256 synthAmount, ISpotMarketProxy.Data memory) { - amount = synthAmount; + function _buy( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance + ) + internal + returns (uint256 received, address synth) + { + IERC20(USDX).approve(SPOT_MARKET, _amount); + try ISpotMarket(SPOT_MARKET).buy({ + marketId: _synthId, + usdAmount: _amount, + minAmountReceived: _tolerance, + referrer: REFERRER + }) returns (uint256 amount, ISpotMarket.Data memory) { + received = amount; + synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); } catch Error(string memory reason) { revert SwapFailed(reason); } + } - IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); - synth.approve(address(_data.spotMarket), amount); + function sell( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance, + address _receiver + ) + external + returns (uint256 received) + { + address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); + _pull(synth, msg.sender, _amount); + received = _sell(_synthId, _amount, _tolerance); + _push(USDX, _receiver, received); + } - try _data.spotMarket.unwrap({ - marketId: _data.marketId, - unwrapAmount: amount, - minAmountReceived: _data.tolerance.tolerableWrapAmount - }) returns ( - uint256 returnCollateralAmount, ISpotMarketProxy.Data memory - ) { - amount = returnCollateralAmount; + function _sell( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance + ) + internal + returns (uint256 received) + { + address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); + IERC20(synth).approve(SPOT_MARKET, _amount); + try ISpotMarket(SPOT_MARKET).sell({ + marketId: _synthId, + synthAmount: _amount, + minUsdAmount: _tolerance, + referrer: REFERRER + }) returns (uint256 amount, ISpotMarket.Data memory) { + received = amount; } catch Error(string memory reason) { - revert UnwrapFailed(reason); + revert SwapFailed(reason); } + } - _data.collateral.transfer(_data.receiver, amount); + function _pull( + address _token, + address _from, + uint256 _amount + ) + internal + returns (bool success) + { + IERC20 token = IERC20(_token); + success = token.transferFrom(_from, address(this), _amount); + } - emit ZapOut( - msg.sender, _data.marketId, _data.amount, amount, _data.receiver + function _push( + address _token, + address _receiver, + uint256 _amount + ) + internal + returns (bool success) + { + if (_receiver == address(this)) { + success = true; + } else { + IERC20 token = IERC20(_token); + success = token.transfer(_receiver, _amount); + } + } + + function requestFlashloan( + uint256 _usdcLoan, + uint256 _collateralAmount, + address _collateral, + uint128 _accountId, + uint128 _synthId, + uint256 _tolerance, + uint256 _swapTolerance, + address receiver + ) + external + { + bytes memory params = abi.encode( + _collateralAmount, + _collateral, + _accountId, + _synthId, + _tolerance, + _swapTolerance, + receiver ); + + IPool(AAVE).flashLoanSimple({ + receiverAddress: address(this), + asset: USDC, + amount: _usdcLoan, + params: params, + referralCode: REFERRAL_CODE + }); } - /// @notice Function to wrap collateral into a synth - /// @param _data ZapData struct containing all necessary information - function wrap(ZapData calldata _data) external { - uint256 amount = _data.amount; + function executeOperation( + address, + uint256 amount, + uint256 premium, + address, + bytes calldata params + ) + external + returns (bool) + { + ( + uint256 collateralAmount, + address collateral, + uint128 accountId, + uint128 synthId, + uint256 tolerance, + uint256 swapTolerance, + address receiver + ) = abi.decode( + params, + (uint256, address, uint128, uint128, uint256, uint256, address) + ); + + uint256 unwoundCollateral = _unwind( + amount, + collateralAmount, + collateral, + accountId, + synthId, + tolerance, + swapTolerance + ); + uint256 debt = amount + premium; + uint256 differece = unwoundCollateral - debt; - _data.collateral.transferFrom(msg.sender, address(this), amount); - _data.collateral.approve(address(_data.spotMarket), amount); + IERC20(USDC).approve(AAVE, debt); - try _data.spotMarket.wrap({ - marketId: _data.marketId, - wrapAmount: amount, - minAmountReceived: _data.tolerance.tolerableWrapAmount - }) returns (uint256 amountToMint, ISpotMarketProxy.Data memory) { - amount = amountToMint; - } catch Error(string memory reason) { - revert WrapFailed(reason); + return IERC20(collateral).transfer(receiver, differece); + } + + function unwind( + uint256 _usdcAmount, + uint256 _collateralAmount, + address _collateral, + uint128 _accountId, + uint128 _synthId, + uint256 _tolerance, + uint256 _swapTolerance, + address _receiver + ) + external + { + _pull(USDC, msg.sender, _usdcAmount); + _unwind( + _usdcAmount, + _collateralAmount, + _collateral, + _accountId, + _synthId, + _tolerance, + _swapTolerance + ); + _push(_collateral, _receiver, _collateralAmount); + } + + function _unwind( + uint256 _usdcLoan, + uint256 _collateralAmount, + address _collateral, + uint128 _accountId, + uint128 _synthId, + uint256 _tolerance, + uint256 _swapTolerance + ) + internal + returns (uint256 unwound) + { + uint256 usdxAmount = _zapIn(_usdcLoan, _tolerance); + _burn(usdxAmount, _accountId); + _withdraw(_synthId, _collateralAmount, _accountId); + unwound = _unwrap(_synthId, _collateralAmount, _tolerance); + + if (_synthId != SUSDC_SPOT_ID) { + _swap(_collateral, unwound, _swapTolerance); } + } - IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); - synth.approve(address(_data.spotMarket), amount); + function burn(uint256 _amount, uint128 _accountId) external { + _pull(USDX, msg.sender, _amount); + _burn(_amount, _accountId); + } - synth.transfer(_data.receiver, amount); + function _burn(uint256 _amount, uint128 _accountId) internal { + IERC20(USDX).approve(CORE, _amount); + ICore(CORE).burnUsd(_accountId, PREFFERED_POOL_ID, USDC, _amount); + } - emit Wrapped( - msg.sender, _data.marketId, _data.amount, amount, _data.receiver - ); + function withdraw( + uint128 _synthId, + uint256 _amount, + uint128 _accountId, + address _receiver + ) + external + { + _withdraw(_synthId, _amount, _accountId); + address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); + _push(synth, _receiver, _amount); } - /// @notice Function to unwrap a synth back into collateral - /// @param _data ZapData struct containing all necessary information - function unwrap(ZapData calldata _data) external { - uint256 amount = _data.amount; + function _withdraw( + uint128 _synthId, + uint256 _amount, + uint128 _accountId + ) + internal + { + IPerpsMarket market = IPerpsMarket(PERPS_MARKET); + bool canModify = + market.hasPermission(_accountId, MODIFY_PERMISSION, address(this)); + require(canModify, NotPermittedToModifyCollateral()); + market.modifyCollateral({ + accountId: _accountId, + synthMarketId: _synthId, + amountDelta: -int256(_amount) + }); + market.renouncePermission(_accountId, MODIFY_PERMISSION); + } - IERC20 synth = IERC20(_data.spotMarket.getSynth(_data.marketId)); - synth.approve(address(_data.spotMarket), amount); + function swap( + address _from, + uint256 _amount, + uint256 _tolerance, + address _receiver + ) + external + returns (uint256 received) + { + _pull(_from, msg.sender, _amount); + received = _swap(_from, _amount, _tolerance); + _push(USDC, _receiver, received); + } - try _data.spotMarket.unwrap({ - marketId: _data.marketId, - unwrapAmount: amount, - minAmountReceived: _data.tolerance.tolerableWrapAmount - }) returns ( - uint256 returnCollateralAmount, ISpotMarketProxy.Data memory + function _swap( + address _from, + uint256 _amount, + uint256 _tolerance + ) + internal + returns (uint256 received) + { + IERC20(_from).approve(UNISWAP, _amount); + + IUniswap.ExactInputSingleParams memory params = IUniswap + .ExactInputSingleParams({ + tokenIn: _from, + tokenOut: USDC, + fee: FEE_TIER, + recipient: address(this), + amountIn: _amount, + amountOutMinimum: _tolerance, + sqrtPriceLimitX96: 0 + }); + + try IUniswap(UNISWAP).exactInputSingle(params) returns ( + uint256 amountOut ) { - amount = returnCollateralAmount; + received = amountOut; } catch Error(string memory reason) { - revert UnwrapFailed(reason); + revert SwapFailed(reason); } - - _data.collateral.transfer(_data.receiver, amount); - - emit Unwrapped( - msg.sender, _data.marketId, _data.amount, amount, _data.receiver - ); } } diff --git a/src/ZapErrors.sol b/src/ZapErrors.sol deleted file mode 100644 index 5926e23..0000000 --- a/src/ZapErrors.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -/// @title Zap contract errors -/// @author JaredBorders (jaredborders@pm.me) -contract ZapErrors { - - /// @notice Thrown when a wrap operation fails - /// @param reason The reason for the failure - error WrapFailed(string reason); - - /// @notice Thrown when an unwrap operation fails - /// @param reason The reason for the failure - error UnwrapFailed(string reason); - - /// @notice Thrown when a swap operation fails - /// @param reason The reason for the failure - error SwapFailed(string reason); - -} diff --git a/src/ZapEvents.sol b/src/ZapEvents.sol deleted file mode 100644 index d1725bd..0000000 --- a/src/ZapEvents.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -/// @title Zap contract events -/// @author JaredBorders (jaredborders@pm.me) -contract ZapEvents { - - /// @notice Emitted when a zap-in operation is performed (Collateral to - /// sUSD) - /// @param initiator Address that initiated the zap-in - /// @param marketId ID of the market involved - /// @param collateralAmount Amount of collateral input - /// @param sUSDAmount Amount of sUSD output - /// @param receiver Address that received the sUSD - event ZapIn( - address indexed initiator, - uint128 indexed marketId, - uint256 collateralAmount, - uint256 sUSDAmount, - address indexed receiver - ); - - /// @notice Emitted when a zap-out operation is performed (sUSD to - /// Collateral) - /// @param initiator Address that initiated the zap-out - /// @param marketId ID of the market involved - /// @param sUSDAmount Amount of sUSD input - /// @param collateralAmount Amount of collateral output - /// @param receiver Address that received the collateral - event ZapOut( - address indexed initiator, - uint128 indexed marketId, - uint256 sUSDAmount, - uint256 collateralAmount, - address indexed receiver - ); - - /// @notice Emitted when a wrap operation is performed - /// @param initiator Address that initiated the wrap - /// @param marketId ID of the market involved - /// @param collateralAmount Amount of collateral wrapped - /// @param synthAmount Amount of synth received - /// @param receiver Address that received the synth tokens - event Wrapped( - address indexed initiator, - uint128 indexed marketId, - uint256 collateralAmount, - uint256 synthAmount, - address indexed receiver - ); - - /// @notice Emitted when an unwrap operation is performed - /// @param initiator Address that initiated the unwrap - /// @param marketId ID of the market involved - /// @param synthAmount Amount of synth unwrapped - /// @param collateralAmount Amount of collateral received - /// @param receiver Address that received the collateral tokens - event Unwrapped( - address indexed initiator, - uint128 indexed marketId, - uint256 synthAmount, - uint256 collateralAmount, - address indexed receiver - ); - -} diff --git a/src/interfaces/IAave.sol b/src/interfaces/IAave.sol new file mode 100644 index 0000000..a59da51 --- /dev/null +++ b/src/interfaces/IAave.sol @@ -0,0 +1,1533 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +library DataTypes { + + struct ReserveData { + //stores the reserve configuration + ReserveConfigurationMap configuration; + //the liquidity index. Expressed in ray + uint128 liquidityIndex; + //the current supply rate. Expressed in ray + uint128 currentLiquidityRate; + //variable borrow index. Expressed in ray + uint128 variableBorrowIndex; + //the current variable borrow rate. Expressed in ray + uint128 currentVariableBorrowRate; + //the current stable borrow rate. Expressed in ray + uint128 currentStableBorrowRate; + //timestamp of last update + uint40 lastUpdateTimestamp; + //the id of the reserve. Represents the position in the list of the + // active reserves + uint16 id; + //aToken address + address aTokenAddress; + //stableDebtToken address + address stableDebtTokenAddress; + //variableDebtToken address + address variableDebtTokenAddress; + //address of the interest rate strategy + address interestRateStrategyAddress; + //the current treasury balance, scaled + uint128 accruedToTreasury; + //the outstanding unbacked aTokens minted through the bridging feature + uint128 unbacked; + //the outstanding debt borrowed against this asset in isolation mode + uint128 isolationModeTotalDebt; + } + + struct ReserveConfigurationMap { + //bit 0-15: LTV + //bit 16-31: Liq. threshold + //bit 32-47: Liq. bonus + //bit 48-55: Decimals + //bit 56: reserve is active + //bit 57: reserve is frozen + //bit 58: borrowing is enabled + //bit 59: stable rate borrowing enabled + //bit 60: asset is paused + //bit 61: borrowing in isolation mode is enabled + //bit 62: siloed borrowing enabled + //bit 63: flashloaning enabled + //bit 64-79: reserve factor + //bit 80-115 borrow cap in whole tokens, borrowCap == 0 => no cap + //bit 116-151 supply cap in whole tokens, supplyCap == 0 => no cap + //bit 152-167 liquidation protocol fee + //bit 168-175 eMode category + //bit 176-211 unbacked mint cap in whole tokens, unbackedMintCap == 0 => + // minting disabled + //bit 212-251 debt ceiling for isolation mode with + // (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals + //bit 252-255 unused + uint256 data; + } + + struct UserConfigurationMap { + /** + * @dev Bitmap of the users collaterals and borrows. It is divided in + * pairs of bits, one pair per asset. + * The first bit indicates if an asset is used as collateral by the + * user, the second whether an + * asset is borrowed by the user. + */ + uint256 data; + } + + struct EModeCategory { + // each eMode category has a custom ltv and liquidation threshold + uint16 ltv; + uint16 liquidationThreshold; + uint16 liquidationBonus; + // each eMode category may or may not have a custom oracle to override + // the individual assets price oracles + address priceSource; + string label; + } + + enum InterestRateMode { + NONE, + STABLE, + VARIABLE + } + + struct ReserveCache { + uint256 currScaledVariableDebt; + uint256 nextScaledVariableDebt; + uint256 currPrincipalStableDebt; + uint256 currAvgStableBorrowRate; + uint256 currTotalStableDebt; + uint256 nextAvgStableBorrowRate; + uint256 nextTotalStableDebt; + uint256 currLiquidityIndex; + uint256 nextLiquidityIndex; + uint256 currVariableBorrowIndex; + uint256 nextVariableBorrowIndex; + uint256 currLiquidityRate; + uint256 currVariableBorrowRate; + uint256 reserveFactor; + ReserveConfigurationMap reserveConfiguration; + address aTokenAddress; + address stableDebtTokenAddress; + address variableDebtTokenAddress; + uint40 reserveLastUpdateTimestamp; + uint40 stableDebtLastUpdateTimestamp; + } + + struct ExecuteLiquidationCallParams { + uint256 reservesCount; + uint256 debtToCover; + address collateralAsset; + address debtAsset; + address user; + bool receiveAToken; + address priceOracle; + uint8 userEModeCategory; + address priceOracleSentinel; + } + + struct ExecuteSupplyParams { + address asset; + uint256 amount; + address onBehalfOf; + uint16 referralCode; + } + + struct ExecuteBorrowParams { + address asset; + address user; + address onBehalfOf; + uint256 amount; + InterestRateMode interestRateMode; + uint16 referralCode; + bool releaseUnderlying; + uint256 maxStableRateBorrowSizePercent; + uint256 reservesCount; + address oracle; + uint8 userEModeCategory; + address priceOracleSentinel; + } + + struct ExecuteRepayParams { + address asset; + uint256 amount; + InterestRateMode interestRateMode; + address onBehalfOf; + bool useATokens; + } + + struct ExecuteWithdrawParams { + address asset; + uint256 amount; + address to; + uint256 reservesCount; + address oracle; + uint8 userEModeCategory; + } + + struct ExecuteSetUserEModeParams { + uint256 reservesCount; + address oracle; + uint8 categoryId; + } + + struct FinalizeTransferParams { + address asset; + address from; + address to; + uint256 amount; + uint256 balanceFromBefore; + uint256 balanceToBefore; + uint256 reservesCount; + address oracle; + uint8 fromEModeCategory; + } + + struct FlashloanParams { + address receiverAddress; + address[] assets; + uint256[] amounts; + uint256[] interestRateModes; + address onBehalfOf; + bytes params; + uint16 referralCode; + uint256 flashLoanPremiumToProtocol; + uint256 flashLoanPremiumTotal; + uint256 maxStableRateBorrowSizePercent; + uint256 reservesCount; + address addressesProvider; + uint8 userEModeCategory; + bool isAuthorizedFlashBorrower; + } + + struct FlashloanSimpleParams { + address receiverAddress; + address asset; + uint256 amount; + bytes params; + uint16 referralCode; + uint256 flashLoanPremiumToProtocol; + uint256 flashLoanPremiumTotal; + } + + struct FlashLoanRepaymentParams { + uint256 amount; + uint256 totalPremium; + uint256 flashLoanPremiumToProtocol; + address asset; + address receiverAddress; + uint16 referralCode; + } + + struct CalculateUserAccountDataParams { + UserConfigurationMap userConfig; + uint256 reservesCount; + address user; + address oracle; + uint8 userEModeCategory; + } + + struct ValidateBorrowParams { + ReserveCache reserveCache; + UserConfigurationMap userConfig; + address asset; + address userAddress; + uint256 amount; + InterestRateMode interestRateMode; + uint256 maxStableLoanPercent; + uint256 reservesCount; + address oracle; + uint8 userEModeCategory; + address priceOracleSentinel; + bool isolationModeActive; + address isolationModeCollateralAddress; + uint256 isolationModeDebtCeiling; + } + + struct ValidateLiquidationCallParams { + ReserveCache debtReserveCache; + uint256 totalDebt; + uint256 healthFactor; + address priceOracleSentinel; + } + + struct CalculateInterestRatesParams { + uint256 unbacked; + uint256 liquidityAdded; + uint256 liquidityTaken; + uint256 totalStableDebt; + uint256 totalVariableDebt; + uint256 averageStableBorrowRate; + uint256 reserveFactor; + address reserve; + address aToken; + } + + struct InitReserveParams { + address asset; + address aTokenAddress; + address stableDebtAddress; + address variableDebtAddress; + address interestRateStrategyAddress; + uint16 reservesCount; + uint16 maxNumberReserves; + } + +} + +/** + * @title IPoolAddressesProvider + * @author Aave + * @notice Defines the basic interface for a Pool Addresses Provider. + */ +interface IPoolAddressesProvider { + + /** + * @dev Emitted when the market identifier is updated. + * @param oldMarketId The old id of the market + * @param newMarketId The new id of the market + */ + event MarketIdSet(string indexed oldMarketId, string indexed newMarketId); + + /** + * @dev Emitted when the pool is updated. + * @param oldAddress The old address of the Pool + * @param newAddress The new address of the Pool + */ + event PoolUpdated(address indexed oldAddress, address indexed newAddress); + + /** + * @dev Emitted when the pool configurator is updated. + * @param oldAddress The old address of the PoolConfigurator + * @param newAddress The new address of the PoolConfigurator + */ + event PoolConfiguratorUpdated( + address indexed oldAddress, address indexed newAddress + ); + + /** + * @dev Emitted when the price oracle is updated. + * @param oldAddress The old address of the PriceOracle + * @param newAddress The new address of the PriceOracle + */ + event PriceOracleUpdated( + address indexed oldAddress, address indexed newAddress + ); + + /** + * @dev Emitted when the ACL manager is updated. + * @param oldAddress The old address of the ACLManager + * @param newAddress The new address of the ACLManager + */ + event ACLManagerUpdated( + address indexed oldAddress, address indexed newAddress + ); + + /** + * @dev Emitted when the ACL admin is updated. + * @param oldAddress The old address of the ACLAdmin + * @param newAddress The new address of the ACLAdmin + */ + event ACLAdminUpdated( + address indexed oldAddress, address indexed newAddress + ); + + /** + * @dev Emitted when the price oracle sentinel is updated. + * @param oldAddress The old address of the PriceOracleSentinel + * @param newAddress The new address of the PriceOracleSentinel + */ + event PriceOracleSentinelUpdated( + address indexed oldAddress, address indexed newAddress + ); + + /** + * @dev Emitted when the pool data provider is updated. + * @param oldAddress The old address of the PoolDataProvider + * @param newAddress The new address of the PoolDataProvider + */ + event PoolDataProviderUpdated( + address indexed oldAddress, address indexed newAddress + ); + + /** + * @dev Emitted when a new proxy is created. + * @param id The identifier of the proxy + * @param proxyAddress The address of the created proxy contract + * @param implementationAddress The address of the implementation contract + */ + event ProxyCreated( + bytes32 indexed id, + address indexed proxyAddress, + address indexed implementationAddress + ); + + /** + * @dev Emitted when a new non-proxied contract address is registered. + * @param id The identifier of the contract + * @param oldAddress The address of the old contract + * @param newAddress The address of the new contract + */ + event AddressSet( + bytes32 indexed id, + address indexed oldAddress, + address indexed newAddress + ); + + /** + * @dev Emitted when the implementation of the proxy registered with id is + * updated + * @param id The identifier of the contract + * @param proxyAddress The address of the proxy contract + * @param oldImplementationAddress The address of the old implementation + * contract + * @param newImplementationAddress The address of the new implementation + * contract + */ + event AddressSetAsProxy( + bytes32 indexed id, + address indexed proxyAddress, + address oldImplementationAddress, + address indexed newImplementationAddress + ); + + /** + * @notice Returns the id of the Aave market to which this contract points + * to. + * @return The market id + */ + function getMarketId() external view returns (string memory); + + /** + * @notice Associates an id with a specific PoolAddressesProvider. + * @dev This can be used to create an onchain registry of + * PoolAddressesProviders to + * identify and validate multiple Aave markets. + * @param newMarketId The market id + */ + function setMarketId(string calldata newMarketId) external; + + /** + * @notice Returns an address by its identifier. + * @dev The returned address might be an EOA or a contract, potentially + * proxied + * @dev It returns ZERO if there is no registered address with the given id + * @param id The id + * @return The address of the registered for the specified id + */ + function getAddress(bytes32 id) external view returns (address); + + /** + * @notice General function to update the implementation of a proxy + * registered with + * certain `id`. If there is no proxy registered, it will instantiate one + * and + * set as implementation the `newImplementationAddress`. + * @dev IMPORTANT Use this function carefully, only for ids that don't have + * an explicit + * setter function, in order to avoid unexpected consequences + * @param id The id + * @param newImplementationAddress The address of the new implementation + */ + function setAddressAsProxy( + bytes32 id, + address newImplementationAddress + ) + external; + + /** + * @notice Sets an address for an id replacing the address saved in the + * addresses map. + * @dev IMPORTANT Use this function carefully, as it will do a hard + * replacement + * @param id The id + * @param newAddress The address to set + */ + function setAddress(bytes32 id, address newAddress) external; + + /** + * @notice Returns the address of the Pool proxy. + * @return The Pool proxy address + */ + function getPool() external view returns (address); + + /** + * @notice Updates the implementation of the Pool, or creates a proxy + * setting the new `pool` implementation when the function is called for the + * first time. + * @param newPoolImpl The new Pool implementation + */ + function setPoolImpl(address newPoolImpl) external; + + /** + * @notice Returns the address of the PoolConfigurator proxy. + * @return The PoolConfigurator proxy address + */ + function getPoolConfigurator() external view returns (address); + + /** + * @notice Updates the implementation of the PoolConfigurator, or creates a + * proxy + * setting the new `PoolConfigurator` implementation when the function is + * called for the first time. + * @param newPoolConfiguratorImpl The new PoolConfigurator implementation + */ + function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) + external; + + /** + * @notice Returns the address of the price oracle. + * @return The address of the PriceOracle + */ + function getPriceOracle() external view returns (address); + + /** + * @notice Updates the address of the price oracle. + * @param newPriceOracle The address of the new PriceOracle + */ + function setPriceOracle(address newPriceOracle) external; + + /** + * @notice Returns the address of the ACL manager. + * @return The address of the ACLManager + */ + function getACLManager() external view returns (address); + + /** + * @notice Updates the address of the ACL manager. + * @param newAclManager The address of the new ACLManager + */ + function setACLManager(address newAclManager) external; + + /** + * @notice Returns the address of the ACL admin. + * @return The address of the ACL admin + */ + function getACLAdmin() external view returns (address); + + /** + * @notice Updates the address of the ACL admin. + * @param newAclAdmin The address of the new ACL admin + */ + function setACLAdmin(address newAclAdmin) external; + + /** + * @notice Returns the address of the price oracle sentinel. + * @return The address of the PriceOracleSentinel + */ + function getPriceOracleSentinel() external view returns (address); + + /** + * @notice Updates the address of the price oracle sentinel. + * @param newPriceOracleSentinel The address of the new PriceOracleSentinel + */ + function setPriceOracleSentinel(address newPriceOracleSentinel) external; + + /** + * @notice Returns the address of the data provider. + * @return The address of the DataProvider + */ + function getPoolDataProvider() external view returns (address); + + /** + * @notice Updates the address of the data provider. + * @param newDataProvider The address of the new DataProvider + */ + function setPoolDataProvider(address newDataProvider) external; + +} + +/** + * @title IPool + * @author Aave + * @notice Defines the basic interface for an Aave Pool. + */ +interface IPool { + + /** + * @dev Emitted on mintUnbacked() + * @param reserve The address of the underlying asset of the reserve + * @param user The address initiating the supply + * @param onBehalfOf The beneficiary of the supplied assets, receiving the + * aTokens + * @param amount The amount of supplied assets + * @param referralCode The referral code used + */ + event MintUnbacked( + address indexed reserve, + address user, + address indexed onBehalfOf, + uint256 amount, + uint16 indexed referralCode + ); + + /** + * @dev Emitted on backUnbacked() + * @param reserve The address of the underlying asset of the reserve + * @param backer The address paying for the backing + * @param amount The amount added as backing + * @param fee The amount paid in fees + */ + event BackUnbacked( + address indexed reserve, + address indexed backer, + uint256 amount, + uint256 fee + ); + + /** + * @dev Emitted on supply() + * @param reserve The address of the underlying asset of the reserve + * @param user The address initiating the supply + * @param onBehalfOf The beneficiary of the supply, receiving the aTokens + * @param amount The amount supplied + * @param referralCode The referral code used + */ + event Supply( + address indexed reserve, + address user, + address indexed onBehalfOf, + uint256 amount, + uint16 indexed referralCode + ); + + /** + * @dev Emitted on withdraw() + * @param reserve The address of the underlying asset being withdrawn + * @param user The address initiating the withdrawal, owner of aTokens + * @param to The address that will receive the underlying + * @param amount The amount to be withdrawn + */ + event Withdraw( + address indexed reserve, + address indexed user, + address indexed to, + uint256 amount + ); + + /** + * @dev Emitted on borrow() and flashLoan() when debt needs to be opened + * @param reserve The address of the underlying asset being borrowed + * @param user The address of the user initiating the borrow(), receiving + * the funds on borrow() or just + * initiator of the transaction on flashLoan() + * @param onBehalfOf The address that will be getting the debt + * @param amount The amount borrowed out + * @param interestRateMode The rate mode: 1 for Stable, 2 for Variable + * @param borrowRate The numeric rate at which the user has borrowed, + * expressed in ray + * @param referralCode The referral code used + */ + event Borrow( + address indexed reserve, + address user, + address indexed onBehalfOf, + uint256 amount, + DataTypes.InterestRateMode interestRateMode, + uint256 borrowRate, + uint16 indexed referralCode + ); + + /** + * @dev Emitted on repay() + * @param reserve The address of the underlying asset of the reserve + * @param user The beneficiary of the repayment, getting his debt reduced + * @param repayer The address of the user initiating the repay(), providing + * the funds + * @param amount The amount repaid + * @param useATokens True if the repayment is done using aTokens, `false` if + * done with underlying asset directly + */ + event Repay( + address indexed reserve, + address indexed user, + address indexed repayer, + uint256 amount, + bool useATokens + ); + + /** + * @dev Emitted on swapBorrowRateMode() + * @param reserve The address of the underlying asset of the reserve + * @param user The address of the user swapping his rate mode + * @param interestRateMode The current interest rate mode of the position + * being swapped: 1 for Stable, 2 for Variable + */ + event SwapBorrowRateMode( + address indexed reserve, + address indexed user, + DataTypes.InterestRateMode interestRateMode + ); + + /** + * @dev Emitted on borrow(), repay() and liquidationCall() when using + * isolated assets + * @param asset The address of the underlying asset of the reserve + * @param totalDebt The total isolation mode debt for the reserve + */ + event IsolationModeTotalDebtUpdated( + address indexed asset, uint256 totalDebt + ); + + /** + * @dev Emitted when the user selects a certain asset category for eMode + * @param user The address of the user + * @param categoryId The category id + */ + event UserEModeSet(address indexed user, uint8 categoryId); + + /** + * @dev Emitted on setUserUseReserveAsCollateral() + * @param reserve The address of the underlying asset of the reserve + * @param user The address of the user enabling the usage as collateral + */ + event ReserveUsedAsCollateralEnabled( + address indexed reserve, address indexed user + ); + + /** + * @dev Emitted on setUserUseReserveAsCollateral() + * @param reserve The address of the underlying asset of the reserve + * @param user The address of the user enabling the usage as collateral + */ + event ReserveUsedAsCollateralDisabled( + address indexed reserve, address indexed user + ); + + /** + * @dev Emitted on rebalanceStableBorrowRate() + * @param reserve The address of the underlying asset of the reserve + * @param user The address of the user for which the rebalance has been + * executed + */ + event RebalanceStableBorrowRate( + address indexed reserve, address indexed user + ); + + /** + * @dev Emitted on flashLoan() + * @param target The address of the flash loan receiver contract + * @param initiator The address initiating the flash loan + * @param asset The address of the asset being flash borrowed + * @param amount The amount flash borrowed + * @param interestRateMode The flashloan mode: 0 for regular flashloan, 1 + * for Stable debt, 2 for Variable debt + * @param premium The fee flash borrowed + * @param referralCode The referral code used + */ + event FlashLoan( + address indexed target, + address initiator, + address indexed asset, + uint256 amount, + DataTypes.InterestRateMode interestRateMode, + uint256 premium, + uint16 indexed referralCode + ); + + /** + * @dev Emitted when a borrower is liquidated. + * @param collateralAsset The address of the underlying asset used as + * collateral, to receive as result of the liquidation + * @param debtAsset The address of the underlying borrowed asset to be + * repaid with the liquidation + * @param user The address of the borrower getting liquidated + * @param debtToCover The debt amount of borrowed `asset` the liquidator + * wants to cover + * @param liquidatedCollateralAmount The amount of collateral received by + * the liquidator + * @param liquidator The address of the liquidator + * @param receiveAToken True if the liquidators wants to receive the + * collateral aTokens, `false` if he wants + * to receive the underlying collateral asset directly + */ + event LiquidationCall( + address indexed collateralAsset, + address indexed debtAsset, + address indexed user, + uint256 debtToCover, + uint256 liquidatedCollateralAmount, + address liquidator, + bool receiveAToken + ); + + /** + * @dev Emitted when the state of a reserve is updated. + * @param reserve The address of the underlying asset of the reserve + * @param liquidityRate The next liquidity rate + * @param stableBorrowRate The next stable borrow rate + * @param variableBorrowRate The next variable borrow rate + * @param liquidityIndex The next liquidity index + * @param variableBorrowIndex The next variable borrow index + */ + event ReserveDataUpdated( + address indexed reserve, + uint256 liquidityRate, + uint256 stableBorrowRate, + uint256 variableBorrowRate, + uint256 liquidityIndex, + uint256 variableBorrowIndex + ); + + /** + * @dev Emitted when the protocol treasury receives minted aTokens from the + * accrued interest. + * @param reserve The address of the reserve + * @param amountMinted The amount minted to the treasury + */ + event MintedToTreasury(address indexed reserve, uint256 amountMinted); + + /** + * @notice Mints an `amount` of aTokens to the `onBehalfOf` + * @param asset The address of the underlying asset to mint + * @param amount The amount to mint + * @param onBehalfOf The address that will receive the aTokens + * @param referralCode Code used to register the integrator originating the + * operation, for potential rewards. + * 0 if the action is executed directly by the user, without any + * middle-man + */ + function mintUnbacked( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) + external; + + /** + * @notice Back the current unbacked underlying with `amount` and pay `fee`. + * @param asset The address of the underlying asset to back + * @param amount The amount to back + * @param fee The amount paid in fees + * @return The backed amount + */ + function backUnbacked( + address asset, + uint256 amount, + uint256 fee + ) + external + returns (uint256); + + /** + * @notice Supplies an `amount` of underlying asset into the reserve, + * receiving in return overlying aTokens. + * - E.g. User supplies 100 USDC and gets in return 100 aUSDC + * @param asset The address of the underlying asset to supply + * @param amount The amount to be supplied + * @param onBehalfOf The address that will receive the aTokens, same as + * msg.sender if the user + * wants to receive them on his own wallet, or a different address if the + * beneficiary of aTokens + * is a different wallet + * @param referralCode Code used to register the integrator originating the + * operation, for potential rewards. + * 0 if the action is executed directly by the user, without any + * middle-man + */ + function supply( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) + external; + + /** + * @notice Supply with transfer approval of asset to be supplied done via + * permit function + * see: https://eips.ethereum.org/EIPS/eip-2612 and + * https://eips.ethereum.org/EIPS/eip-713 + * @param asset The address of the underlying asset to supply + * @param amount The amount to be supplied + * @param onBehalfOf The address that will receive the aTokens, same as + * msg.sender if the user + * wants to receive them on his own wallet, or a different address if the + * beneficiary of aTokens + * is a different wallet + * @param deadline The deadline timestamp that the permit is valid + * @param referralCode Code used to register the integrator originating the + * operation, for potential rewards. + * 0 if the action is executed directly by the user, without any + * middle-man + * @param permitV The V parameter of ERC712 permit sig + * @param permitR The R parameter of ERC712 permit sig + * @param permitS The S parameter of ERC712 permit sig + */ + function supplyWithPermit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) + external; + + /** + * @notice Withdraws an `amount` of underlying asset from the reserve, + * burning the equivalent aTokens owned + * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning + * the 100 aUSDC + * @param asset The address of the underlying asset to withdraw + * @param amount The underlying amount to be withdrawn + * - Send the value type(uint256).max in order to withdraw the whole + * aToken balance + * @param to The address that will receive the underlying, same as + * msg.sender if the user + * wants to receive it on his own wallet, or a different address if the + * beneficiary is a + * different wallet + * @return The final amount withdrawn + */ + function withdraw( + address asset, + uint256 amount, + address to + ) + external + returns (uint256); + + /** + * @notice Allows users to borrow a specific `amount` of the reserve + * underlying asset, provided that the borrower + * already supplied enough collateral, or he was given enough allowance by a + * credit delegator on the + * corresponding debt token (StableDebtToken or VariableDebtToken) + * - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, + * receiving the 100 USDC in his wallet + * and 100 stable/variable debt tokens, depending on the + * `interestRateMode` + * @param asset The address of the underlying asset to borrow + * @param amount The amount to be borrowed + * @param interestRateMode The interest rate mode at which the user wants to + * borrow: 1 for Stable, 2 for Variable + * @param referralCode The code used to register the integrator originating + * the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any + * middle-man + * @param onBehalfOf The address of the user who will receive the debt. + * Should be the address of the borrower itself + * calling the function if he wants to borrow against his own collateral, or + * the address of the credit delegator + * if he has been given credit delegation allowance + */ + function borrow( + address asset, + uint256 amount, + uint256 interestRateMode, + uint16 referralCode, + address onBehalfOf + ) + external; + + /** + * @notice Repays a borrowed `amount` on a specific reserve, burning the + * equivalent debt tokens owned + * - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of + * the `onBehalfOf` address + * @param asset The address of the borrowed underlying asset previously + * borrowed + * @param amount The amount to repay + * - Send the value type(uint256).max in order to repay the whole debt for + * `asset` on the specific `debtMode` + * @param interestRateMode The interest rate mode at of the debt the user + * wants to repay: 1 for Stable, 2 for Variable + * @param onBehalfOf The address of the user who will get his debt + * reduced/removed. Should be the address of the + * user calling the function if he wants to reduce/remove his own debt, or + * the address of any other + * other borrower whose debt should be removed + * @return The final amount repaid + */ + function repay( + address asset, + uint256 amount, + uint256 interestRateMode, + address onBehalfOf + ) + external + returns (uint256); + + /** + * @notice Repay with transfer approval of asset to be repaid done via + * permit function + * see: https://eips.ethereum.org/EIPS/eip-2612 and + * https://eips.ethereum.org/EIPS/eip-713 + * @param asset The address of the borrowed underlying asset previously + * borrowed + * @param amount The amount to repay + * - Send the value type(uint256).max in order to repay the whole debt for + * `asset` on the specific `debtMode` + * @param interestRateMode The interest rate mode at of the debt the user + * wants to repay: 1 for Stable, 2 for Variable + * @param onBehalfOf Address of the user who will get his debt + * reduced/removed. Should be the address of the + * user calling the function if he wants to reduce/remove his own debt, or + * the address of any other + * other borrower whose debt should be removed + * @param deadline The deadline timestamp that the permit is valid + * @param permitV The V parameter of ERC712 permit sig + * @param permitR The R parameter of ERC712 permit sig + * @param permitS The S parameter of ERC712 permit sig + * @return The final amount repaid + */ + function repayWithPermit( + address asset, + uint256 amount, + uint256 interestRateMode, + address onBehalfOf, + uint256 deadline, + uint8 permitV, + bytes32 permitR, + bytes32 permitS + ) + external + returns (uint256); + + /** + * @notice Repays a borrowed `amount` on a specific reserve using the + * reserve aTokens, burning the + * equivalent debt tokens + * - E.g. User repays 100 USDC using 100 aUSDC, burning 100 variable/stable + * debt tokens + * @dev Passing uint256.max as amount will clean up any residual aToken + * dust balance, if the user aToken + * balance is not enough to cover the whole debt + * @param asset The address of the borrowed underlying asset previously + * borrowed + * @param amount The amount to repay + * - Send the value type(uint256).max in order to repay the whole debt for + * `asset` on the specific `debtMode` + * @param interestRateMode The interest rate mode at of the debt the user + * wants to repay: 1 for Stable, 2 for Variable + * @return The final amount repaid + */ + function repayWithATokens( + address asset, + uint256 amount, + uint256 interestRateMode + ) + external + returns (uint256); + + /** + * @notice Allows a borrower to swap his debt between stable and variable + * mode, or vice versa + * @param asset The address of the underlying asset borrowed + * @param interestRateMode The current interest rate mode of the position + * being swapped: 1 for Stable, 2 for Variable + */ + function swapBorrowRateMode( + address asset, + uint256 interestRateMode + ) + external; + + /** + * @notice Rebalances the stable interest rate of a user to the current + * stable rate defined on the reserve. + * - Users can be rebalanced if the following conditions are satisfied: + * 1. Usage ratio is above 95% + * 2. the current supply APY is below REBALANCE_UP_THRESHOLD * + * maxVariableBorrowRate, which means that too + * much has been borrowed at a stable rate and suppliers are not + * earning enough + * @param asset The address of the underlying asset borrowed + * @param user The address of the user to be rebalanced + */ + function rebalanceStableBorrowRate(address asset, address user) external; + + /** + * @notice Allows suppliers to enable/disable a specific supplied asset as + * collateral + * @param asset The address of the underlying asset supplied + * @param useAsCollateral True if the user wants to use the supply as + * collateral, false otherwise + */ + function setUserUseReserveAsCollateral( + address asset, + bool useAsCollateral + ) + external; + + /** + * @notice Function to liquidate a non-healthy position collateral-wise, + * with Health Factor below 1 + * - The caller (liquidator) covers `debtToCover` amount of debt of the user + * getting liquidated, and receives + * a proportionally amount of the `collateralAsset` plus a bonus to cover + * market risk + * @param collateralAsset The address of the underlying asset used as + * collateral, to receive as result of the liquidation + * @param debtAsset The address of the underlying borrowed asset to be + * repaid with the liquidation + * @param user The address of the borrower getting liquidated + * @param debtToCover The debt amount of borrowed `asset` the liquidator + * wants to cover + * @param receiveAToken True if the liquidators wants to receive the + * collateral aTokens, `false` if he wants + * to receive the underlying collateral asset directly + */ + function liquidationCall( + address collateralAsset, + address debtAsset, + address user, + uint256 debtToCover, + bool receiveAToken + ) + external; + + /** + * @notice Allows smartcontracts to access the liquidity of the pool within + * one transaction, + * as long as the amount taken plus a fee is returned. + * @dev IMPORTANT There are security concerns for developers of flashloan + * receiver contracts that must be kept + * into consideration. For further details please visit + * https://docs.aave.com/developers/ + * @param receiverAddress The address of the contract receiving the funds, + * implementing IFlashLoanReceiver interface + * @param assets The addresses of the assets being flash-borrowed + * @param amounts The amounts of the assets being flash-borrowed + * @param interestRateModes Types of the debt to open if the flash loan is + * not returned: + * 0 -> Don't open any debt, just revert if funds can't be transferred + * from the receiver + * 1 -> Open debt at stable rate for the value of the amount + * flash-borrowed to the `onBehalfOf` address + * 2 -> Open debt at variable rate for the value of the amount + * flash-borrowed to the `onBehalfOf` address + * @param onBehalfOf The address that will receive the debt in the case of + * using on `modes` 1 or 2 + * @param params Variadic packed params to pass to the receiver as extra + * information + * @param referralCode The code used to register the integrator originating + * the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any + * middle-man + */ + function flashLoan( + address receiverAddress, + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata interestRateModes, + address onBehalfOf, + bytes calldata params, + uint16 referralCode + ) + external; + + /** + * @notice Allows smartcontracts to access the liquidity of the pool within + * one transaction, + * as long as the amount taken plus a fee is returned. + * @dev IMPORTANT There are security concerns for developers of flashloan + * receiver contracts that must be kept + * into consideration. For further details please visit + * https://docs.aave.com/developers/ + * @param receiverAddress The address of the contract receiving the funds, + * implementing IFlashLoanSimpleReceiver interface + * @param asset The address of the asset being flash-borrowed + * @param amount The amount of the asset being flash-borrowed + * @param params Variadic packed params to pass to the receiver as extra + * information + * @param referralCode The code used to register the integrator originating + * the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any + * middle-man + */ + function flashLoanSimple( + address receiverAddress, + address asset, + uint256 amount, + bytes calldata params, + uint16 referralCode + ) + external; + + /** + * @notice Returns the user account data across all the reserves + * @param user The address of the user + * @return totalCollateralBase The total collateral of the user in the base + * currency used by the price feed + * @return totalDebtBase The total debt of the user in the base currency + * used by the price feed + * @return availableBorrowsBase The borrowing power left of the user in the + * base currency used by the price feed + * @return currentLiquidationThreshold The liquidation threshold of the user + * @return ltv The loan to value of The user + * @return healthFactor The current health factor of the user + */ + function getUserAccountData(address user) + external + view + returns ( + uint256 totalCollateralBase, + uint256 totalDebtBase, + uint256 availableBorrowsBase, + uint256 currentLiquidationThreshold, + uint256 ltv, + uint256 healthFactor + ); + + /** + * @notice Initializes a reserve, activating it, assigning an aToken and + * debt tokens and an + * interest rate strategy + * @dev Only callable by the PoolConfigurator contract + * @param asset The address of the underlying asset of the reserve + * @param aTokenAddress The address of the aToken that will be assigned to + * the reserve + * @param stableDebtAddress The address of the StableDebtToken that will be + * assigned to the reserve + * @param variableDebtAddress The address of the VariableDebtToken that will + * be assigned to the reserve + * @param interestRateStrategyAddress The address of the interest rate + * strategy contract + */ + function initReserve( + address asset, + address aTokenAddress, + address stableDebtAddress, + address variableDebtAddress, + address interestRateStrategyAddress + ) + external; + + /** + * @notice Drop a reserve + * @dev Only callable by the PoolConfigurator contract + * @param asset The address of the underlying asset of the reserve + */ + function dropReserve(address asset) external; + + /** + * @notice Updates the address of the interest rate strategy contract + * @dev Only callable by the PoolConfigurator contract + * @param asset The address of the underlying asset of the reserve + * @param rateStrategyAddress The address of the interest rate strategy + * contract + */ + function setReserveInterestRateStrategyAddress( + address asset, + address rateStrategyAddress + ) + external; + + /** + * @notice Sets the configuration bitmap of the reserve as a whole + * @dev Only callable by the PoolConfigurator contract + * @param asset The address of the underlying asset of the reserve + * @param configuration The new configuration bitmap + */ + function setConfiguration( + address asset, + DataTypes.ReserveConfigurationMap calldata configuration + ) + external; + + /** + * @notice Returns the configuration of the reserve + * @param asset The address of the underlying asset of the reserve + * @return The configuration of the reserve + */ + function getConfiguration(address asset) + external + view + returns (DataTypes.ReserveConfigurationMap memory); + + /** + * @notice Returns the configuration of the user across all the reserves + * @param user The user address + * @return The configuration of the user + */ + function getUserConfiguration(address user) + external + view + returns (DataTypes.UserConfigurationMap memory); + + /** + * @notice Returns the normalized income of the reserve + * @param asset The address of the underlying asset of the reserve + * @return The reserve's normalized income + */ + function getReserveNormalizedIncome(address asset) + external + view + returns (uint256); + + /** + * @notice Returns the normalized variable debt per unit of asset + * @dev WARNING: This function is intended to be used primarily by the + * protocol itself to get a + * "dynamic" variable index based on time, current stored index and virtual + * rate at the current + * moment (approx. a borrower would get if opening a position). This means + * that is always used in + * combination with variable debt supply/balances. + * If using this function externally, consider that is possible to have an + * increasing normalized + * variable debt that is not equivalent to how the variable debt index would + * be updated in storage + * (e.g. only updates with non-zero variable debt supply) + * @param asset The address of the underlying asset of the reserve + * @return The reserve normalized variable debt + */ + function getReserveNormalizedVariableDebt(address asset) + external + view + returns (uint256); + + /** + * @notice Returns the state and configuration of the reserve + * @param asset The address of the underlying asset of the reserve + * @return The state and configuration data of the reserve + */ + function getReserveData(address asset) + external + view + returns (DataTypes.ReserveData memory); + + /** + * @notice Validates and finalizes an aToken transfer + * @dev Only callable by the overlying aToken of the `asset` + * @param asset The address of the underlying asset of the aToken + * @param from The user from which the aTokens are transferred + * @param to The user receiving the aTokens + * @param amount The amount being transferred/withdrawn + * @param balanceFromBefore The aToken balance of the `from` user before the + * transfer + * @param balanceToBefore The aToken balance of the `to` user before the + * transfer + */ + function finalizeTransfer( + address asset, + address from, + address to, + uint256 amount, + uint256 balanceFromBefore, + uint256 balanceToBefore + ) + external; + + /** + * @notice Returns the list of the underlying assets of all the initialized + * reserves + * @dev It does not include dropped reserves + * @return The addresses of the underlying assets of the initialized + * reserves + */ + function getReservesList() external view returns (address[] memory); + + /** + * @notice Returns the address of the underlying asset of a reserve by the + * reserve id as stored in the DataTypes.ReserveData struct + * @param id The id of the reserve as stored in the DataTypes.ReserveData + * struct + * @return The address of the reserve associated with id + */ + function getReserveAddressById(uint16 id) external view returns (address); + + /** + * @notice Returns the PoolAddressesProvider connected to this contract + * @return The address of the PoolAddressesProvider + */ + function ADDRESSES_PROVIDER() + external + view + returns (IPoolAddressesProvider); + + /** + * @notice Updates the protocol fee on the bridging + * @param bridgeProtocolFee The part of the premium sent to the protocol + * treasury + */ + function updateBridgeProtocolFee(uint256 bridgeProtocolFee) external; + + /** + * @notice Updates flash loan premiums. Flash loan premium consists of two + * parts: + * - A part is sent to aToken holders as extra, one time accumulated + * interest + * - A part is collected by the protocol treasury + * @dev The total premium is calculated on the total borrowed amount + * @dev The premium to protocol is calculated on the total premium, being a + * percentage of `flashLoanPremiumTotal` + * @dev Only callable by the PoolConfigurator contract + * @param flashLoanPremiumTotal The total premium, expressed in bps + * @param flashLoanPremiumToProtocol The part of the premium sent to the + * protocol treasury, expressed in bps + */ + function updateFlashloanPremiums( + uint128 flashLoanPremiumTotal, + uint128 flashLoanPremiumToProtocol + ) + external; + + /** + * @notice Configures a new category for the eMode. + * @dev In eMode, the protocol allows very high borrowing power to borrow + * assets of the same category. + * The category 0 is reserved as it's the default for volatile assets + * @param id The id of the category + * @param config The configuration of the category + */ + function configureEModeCategory( + uint8 id, + DataTypes.EModeCategory memory config + ) + external; + + /** + * @notice Returns the data of an eMode category + * @param id The id of the category + * @return The configuration data of the category + */ + function getEModeCategoryData(uint8 id) + external + view + returns (DataTypes.EModeCategory memory); + + /** + * @notice Allows a user to use the protocol in eMode + * @param categoryId The id of the category + */ + function setUserEMode(uint8 categoryId) external; + + /** + * @notice Returns the eMode the user is using + * @param user The address of the user + * @return The eMode id + */ + function getUserEMode(address user) external view returns (uint256); + + /** + * @notice Resets the isolation mode total debt of the given asset to zero + * @dev It requires the given asset has zero debt ceiling + * @param asset The address of the underlying asset to reset the + * isolationModeTotalDebt + */ + function resetIsolationModeTotalDebt(address asset) external; + + /** + * @notice Returns the percentage of available liquidity that can be + * borrowed at once at stable rate + * @return The percentage of available liquidity to borrow, expressed in bps + */ + function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() + external + view + returns (uint256); + + /** + * @notice Returns the total fee on flash loans + * @return The total fee on flashloans + */ + function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128); + + /** + * @notice Returns the part of the bridge fees sent to protocol + * @return The bridge fee sent to the protocol treasury + */ + function BRIDGE_PROTOCOL_FEE() external view returns (uint256); + + /** + * @notice Returns the part of the flashloan fees sent to protocol + * @return The flashloan fee sent to the protocol treasury + */ + function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint128); + + /** + * @notice Returns the maximum number of reserves supported to be listed in + * this Pool + * @return The maximum number of reserves supported + */ + function MAX_NUMBER_RESERVES() external view returns (uint16); + + /** + * @notice Mints the assets accrued through the reserve factor to the + * treasury in the form of aTokens + * @param assets The list of reserves for which the minting needs to be + * executed + */ + function mintToTreasury(address[] calldata assets) external; + + /** + * @notice Rescue and transfer tokens locked in this contract + * @param token The address of the token + * @param to The address of the recipient + * @param amount The amount of token to transfer + */ + function rescueTokens(address token, address to, uint256 amount) external; + + /** + * @notice Supplies an `amount` of underlying asset into the reserve, + * receiving in return overlying aTokens. + * - E.g. User supplies 100 USDC and gets in return 100 aUSDC + * @dev Deprecated: Use the `supply` function instead + * @param asset The address of the underlying asset to supply + * @param amount The amount to be supplied + * @param onBehalfOf The address that will receive the aTokens, same as + * msg.sender if the user + * wants to receive them on his own wallet, or a different address if the + * beneficiary of aTokens + * is a different wallet + * @param referralCode Code used to register the integrator originating the + * operation, for potential rewards. + * 0 if the action is executed directly by the user, without any + * middle-man + */ + function deposit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) + external; + +} + +/** + * @title IFlashLoanSimpleReceiver + * @author Aave + * @notice Defines the basic interface of a flashloan-receiver contract. + * @dev Implement this interface to develop a flashloan-compatible + * flashLoanReceiver contract + */ +interface IFlashLoanSimpleReceiver { + + /** + * @notice Executes an operation after receiving the flash-borrowed asset + * @dev Ensure that the contract can return the debt + premium, e.g., has + * enough funds to repay and has approved the Pool to pull the total + * amount + * @param asset The address of the flash-borrowed asset + * @param amount The amount of the flash-borrowed asset + * @param premium The fee of the flash-borrowed asset + * @param initiator The address of the flashloan initiator + * @param params The byte-encoded params passed when initiating the + * flashloan + * @return True if the execution of the operation succeeds, false otherwise + */ + function executeOperation( + address asset, + uint256 amount, + uint256 premium, + address initiator, + bytes calldata params + ) + external + returns (bool); + + function ADDRESSES_PROVIDER() + external + view + returns (IPoolAddressesProvider); + + function POOL() external view returns (IPool); + +} diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 8bd6976..cc89afe 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; /// @title Reduced Interface of the ERC20 standard as defined in the EIP /// @author OpenZeppelin diff --git a/src/interfaces/ISpotMarketProxy.sol b/src/interfaces/ISpotMarketProxy.sol deleted file mode 100644 index 18c9478..0000000 --- a/src/interfaces/ISpotMarketProxy.sol +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -/// @title Consolidated Spot Market Proxy Interface -/// @notice Responsible for interacting with Synthetix v3 spot markets -/// @author Synthetix -interface ISpotMarketProxy { - - /*////////////////////////////////////////////////////////////// - MARKET INTERFACE - //////////////////////////////////////////////////////////////*/ - - /// @notice returns a human-readable name for a given market - function name(uint128 marketId) external view returns (string memory); - - /*////////////////////////////////////////////////////////////// - SPOT MARKET FACTORY MODULE - //////////////////////////////////////////////////////////////*/ - - /// @notice Get the proxy address of the synth for the provided marketId - /// @dev Uses associated systems module to retrieve the token address. - /// @param marketId id of the market - /// @return synthAddress address of the proxy for the synth - function getSynth( - uint128 marketId - ) - external - view - returns (address synthAddress); - - /*////////////////////////////////////////////////////////////// - WRAPPER MODULE - //////////////////////////////////////////////////////////////*/ - - struct Data { - uint256 fixedFees; - uint256 utilizationFees; - int256 skewFees; - int256 wrapperFees; - } - - /// @notice Wraps the specified amount and returns similar value of synth - /// minus the fees. - /// @dev Fees are collected from the user by way of the contract returning - /// less synth than specified amount of collateral. - /// @param marketId Id of the market used for the trade. - /// @param wrapAmount Amount of collateral to wrap. This amount gets - /// deposited into the market collateral manager. - /// @param minAmountReceived The minimum amount of synths the trader is - /// expected to receive, otherwise the transaction will revert. - /// @return amountToMint Amount of synth returned to user. - /// @return fees breakdown of all fees. in this case, only wrapper fees are - /// returned. - function wrap( - uint128 marketId, - uint256 wrapAmount, - uint256 minAmountReceived - ) - external - returns (uint256 amountToMint, Data memory fees); - - /// @notice Unwraps the synth and returns similar value of collateral minus - /// the fees. - /// @dev Transfers the specified synth, collects fees through configured fee - /// collector, returns collateral minus fees to trader. - /// @param marketId Id of the market used for the trade. - /// @param unwrapAmount Amount of synth trader is unwrapping. - /// @param minAmountReceived The minimum amount of collateral the trader is - /// expected to receive, otherwise the transaction will revert. - /// @return returnCollateralAmount Amount of collateral returned. - /// @return fees breakdown of all fees. in this case, only wrapper fees are - /// returned. - function unwrap( - uint128 marketId, - uint256 unwrapAmount, - uint256 minAmountReceived - ) - external - returns (uint256 returnCollateralAmount, Data memory fees); - - /*////////////////////////////////////////////////////////////// - ATOMIC ORDER MODULE - //////////////////////////////////////////////////////////////*/ - - /// @notice Initiates a buy trade returning synth for the specified - /// amountUsd. - /// @dev Transfers the specified amountUsd, collects fees through configured - /// fee collector, returns synth to the trader. - /// @dev Leftover fees not collected get deposited into the market manager - /// to improve market PnL. - /// @dev Uses the buyFeedId configured for the market. - /// @param marketId Id of the market used for the trade. - /// @param usdAmount Amount of snxUSD trader is providing allowance for the - /// trade. - /// @param minAmountReceived Min Amount of synth is expected the trader to - /// receive otherwise the transaction will revert. - /// @param referrer Optional address of the referrer, for fee share - /// @return synthAmount Synth received on the trade based on amount provided - /// by trader. - /// @return fees breakdown of all the fees incurred for the transaction. - function buy( - uint128 marketId, - uint256 usdAmount, - uint256 minAmountReceived, - address referrer - ) - external - returns (uint256 synthAmount, Data memory fees); - - /// @notice Initiates a sell trade returning snxUSD for the specified amount - /// of synth (sellAmount) - /// @dev Transfers the specified synth, collects fees through configured fee - /// collector, returns snxUSD to the trader. - /// @dev Leftover fees not collected get deposited into the market manager - /// to improve market PnL. - /// @param marketId Id of the market used for the trade. - /// @param synthAmount Amount of synth provided by trader for trade into - /// snxUSD. - /// @param minUsdAmount Min Amount of snxUSD trader expects to receive for - /// the trade - /// @param referrer Optional address of the referrer, for fee share - /// @return usdAmountReceived Amount of snxUSD returned to user - /// @return fees breakdown of all the fees incurred for the transaction. - function sell( - uint128 marketId, - uint256 synthAmount, - uint256 minUsdAmount, - address referrer - ) - external - returns (uint256 usdAmountReceived, Data memory fees); - -} diff --git a/src/interfaces/ISynthetix.sol b/src/interfaces/ISynthetix.sol new file mode 100644 index 0000000..eb563be --- /dev/null +++ b/src/interfaces/ISynthetix.sol @@ -0,0 +1,1123 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +/// @title Consolidated Spot Market Proxy Interface +/// @notice Responsible for interacting with Synthetix v3 spot markets +/// @author Synthetix +interface ISpotMarket { + + /*////////////////////////////////////////////////////////////// + MARKET INTERFACE + //////////////////////////////////////////////////////////////*/ + + /// @notice returns a human-readable name for a given market + function name(uint128 marketId) external view returns (string memory); + + /*////////////////////////////////////////////////////////////// + SPOT MARKET FACTORY MODULE + //////////////////////////////////////////////////////////////*/ + + /// @notice Get the proxy address of the synth for the provided marketId + /// @dev Uses associated systems module to retrieve the token address. + /// @param marketId id of the market + /// @return synthAddress address of the proxy for the synth + function getSynth(uint128 marketId) + external + view + returns (address synthAddress); + + /*////////////////////////////////////////////////////////////// + WRAPPER MODULE + //////////////////////////////////////////////////////////////*/ + + struct Data { + uint256 fixedFees; + uint256 utilizationFees; + int256 skewFees; + int256 wrapperFees; + } + + /// @notice Wraps the specified amount and returns similar value of synth + /// minus the fees. + /// @dev Fees are collected from the user by way of the contract returning + /// less synth than specified amount of collateral. + /// @param marketId Id of the market used for the trade. + /// @param wrapAmount Amount of collateral to wrap. This amount gets + /// deposited into the market collateral manager. + /// @param minAmountReceived The minimum amount of synths the trader is + /// expected to receive, otherwise the transaction will revert. + /// @return amountToMint Amount of synth returned to user. + /// @return fees breakdown of all fees. in this case, only wrapper fees are + /// returned. + function wrap( + uint128 marketId, + uint256 wrapAmount, + uint256 minAmountReceived + ) + external + returns (uint256 amountToMint, Data memory fees); + + /// @notice Unwraps the synth and returns similar value of collateral minus + /// the fees. + /// @dev Transfers the specified synth, collects fees through configured fee + /// collector, returns collateral minus fees to trader. + /// @param marketId Id of the market used for the trade. + /// @param unwrapAmount Amount of synth trader is unwrapping. + /// @param minAmountReceived The minimum amount of collateral the trader is + /// expected to receive, otherwise the transaction will revert. + /// @return returnCollateralAmount Amount of collateral returned. + /// @return fees breakdown of all fees. in this case, only wrapper fees are + /// returned. + function unwrap( + uint128 marketId, + uint256 unwrapAmount, + uint256 minAmountReceived + ) + external + returns (uint256 returnCollateralAmount, Data memory fees); + + /*////////////////////////////////////////////////////////////// + ATOMIC ORDER MODULE + //////////////////////////////////////////////////////////////*/ + + /// @notice Initiates a buy trade returning synth for the specified + /// amountUsd. + /// @dev Transfers the specified amountUsd, collects fees through configured + /// fee collector, returns synth to the trader. + /// @dev Leftover fees not collected get deposited into the market manager + /// to improve market PnL. + /// @dev Uses the buyFeedId configured for the market. + /// @param marketId Id of the market used for the trade. + /// @param usdAmount Amount of snxUSD trader is providing allowance for the + /// trade. + /// @param minAmountReceived Min Amount of synth is expected the trader to + /// receive otherwise the transaction will revert. + /// @param referrer Optional address of the referrer, for fee share + /// @return synthAmount Synth received on the trade based on amount provided + /// by trader. + /// @return fees breakdown of all the fees incurred for the transaction. + function buy( + uint128 marketId, + uint256 usdAmount, + uint256 minAmountReceived, + address referrer + ) + external + returns (uint256 synthAmount, Data memory fees); + + /// @notice Initiates a sell trade returning snxUSD for the specified amount + /// of synth (sellAmount) + /// @dev Transfers the specified synth, collects fees through configured fee + /// collector, returns snxUSD to the trader. + /// @dev Leftover fees not collected get deposited into the market manager + /// to improve market PnL. + /// @param marketId Id of the market used for the trade. + /// @param synthAmount Amount of synth provided by trader for trade into + /// snxUSD. + /// @param minUsdAmount Min Amount of snxUSD trader expects to receive for + /// the trade + /// @param referrer Optional address of the referrer, for fee share + /// @return usdAmountReceived Amount of snxUSD returned to user + /// @return fees breakdown of all the fees incurred for the transaction. + function sell( + uint128 marketId, + uint256 synthAmount, + uint256 minUsdAmount, + address referrer + ) + external + returns (uint256 usdAmountReceived, Data memory fees); + +} + +interface IPerpsMarket { + + /// @notice modify the collateral delegated to the account + /// @param accountId id of the account + /// @param synthMarketId id of the synth market used as collateral + /// @param amountDelta requested change of collateral delegated + function modifyCollateral( + uint128 accountId, + uint128 synthMarketId, + int256 amountDelta + ) + external; + + function hasPermission( + uint128 accountId, + bytes32 permission, + address user + ) + external + view + returns (bool); + + function renouncePermission( + uint128 accountId, + bytes32 permission + ) + external; + +} + +interface ICore { + + error ImplementationIsSterile(address implementation); + error NoChange(); + error NotAContract(address contr); + error NotNominated(address addr); + error Unauthorized(address addr); + error UpgradeSimulationFailed(); + error ZeroAddress(); + + event OwnerChanged(address oldOwner, address newOwner); + event OwnerNominated(address newOwner); + event Upgraded(address indexed self, address implementation); + + function acceptOwnership() external; + + function getImplementation() external view returns (address); + + function nominateNewOwner(address newNominatedOwner) external; + + function nominatedOwner() external view returns (address); + + function owner() external view returns (address); + + function renounceNomination() external; + + function simulateUpgradeTo(address newImplementation) external; + + function upgradeTo(address newImplementation) external; + + error ValueAlreadyInSet(); + error ValueNotInSet(); + + event FeatureFlagAllowAllSet(bytes32 indexed feature, bool allowAll); + event FeatureFlagAllowlistAdded(bytes32 indexed feature, address account); + event FeatureFlagAllowlistRemoved(bytes32 indexed feature, address account); + event FeatureFlagDeniersReset(bytes32 indexed feature, address[] deniers); + event FeatureFlagDenyAllSet(bytes32 indexed feature, bool denyAll); + + function addToFeatureFlagAllowlist( + bytes32 feature, + address account + ) + external; + + function getDeniers(bytes32 feature) + external + view + returns (address[] memory); + + function getFeatureFlagAllowAll(bytes32 feature) + external + view + returns (bool); + + function getFeatureFlagAllowlist(bytes32 feature) + external + view + returns (address[] memory); + + function getFeatureFlagDenyAll(bytes32 feature) + external + view + returns (bool); + + function isFeatureAllowed( + bytes32 feature, + address account + ) + external + view + returns (bool); + + function removeFromFeatureFlagAllowlist( + bytes32 feature, + address account + ) + external; + + function setDeniers(bytes32 feature, address[] memory deniers) external; + + function setFeatureFlagAllowAll(bytes32 feature, bool allowAll) external; + + function setFeatureFlagDenyAll(bytes32 feature, bool denyAll) external; + + error FeatureUnavailable(bytes32 which); + error InvalidAccountId(uint128 accountId); + error InvalidPermission(bytes32 permission); + error OnlyAccountTokenProxy(address origin); + error PermissionDenied( + uint128 accountId, bytes32 permission, address target + ); + error PermissionNotGranted( + uint128 accountId, bytes32 permission, address user + ); + error PositionOutOfBounds(); + + event AccountCreated(uint128 indexed accountId, address indexed owner); + event PermissionGranted( + uint128 indexed accountId, + bytes32 indexed permission, + address indexed user, + address sender + ); + event PermissionRevoked( + uint128 indexed accountId, + bytes32 indexed permission, + address indexed user, + address sender + ); + + function createAccount() external returns (uint128 accountId); + + function createAccount(uint128 requestedAccountId) external; + + function getAccountLastInteraction(uint128 accountId) + external + view + returns (uint256); + + function getAccountOwner(uint128 accountId) + external + view + returns (address); + + function getAccountPermissions(uint128 accountId) + external + view + returns (IAccountModule.AccountPermissions[] memory accountPerms); + + function getAccountTokenAddress() external view returns (address); + + function grantPermission( + uint128 accountId, + bytes32 permission, + address user + ) + external; + + function hasPermission( + uint128 accountId, + bytes32 permission, + address user + ) + external + view + returns (bool); + + function isAuthorized( + uint128 accountId, + bytes32 permission, + address user + ) + external + view + returns (bool); + + function notifyAccountTransfer(address to, uint128 accountId) external; + + function renouncePermission( + uint128 accountId, + bytes32 permission + ) + external; + + function revokePermission( + uint128 accountId, + bytes32 permission, + address user + ) + external; + + error AccountNotFound(uint128 accountId); + error EmptyDistribution(); + error InsufficientCollateralRatio( + uint256 collateralValue, uint256 debt, uint256 ratio, uint256 minRatio + ); + error MarketNotFound(uint128 marketId); + error NotFundedByPool(uint256 marketId, uint256 poolId); + error OverflowInt256ToInt128(); + error OverflowInt256ToUint256(); + error OverflowUint128ToInt128(); + error OverflowUint256ToInt256(); + error OverflowUint256ToUint128(); + + event DebtAssociated( + uint128 indexed marketId, + uint128 indexed poolId, + address indexed collateralType, + uint128 accountId, + uint256 amount, + int256 updatedDebt + ); + + function associateDebt( + uint128 marketId, + uint128 poolId, + address collateralType, + uint128 accountId, + uint256 amount + ) + external + returns (int256); + + error MismatchAssociatedSystemKind(bytes32 expected, bytes32 actual); + error MissingAssociatedSystem(bytes32 id); + + event AssociatedSystemSet( + bytes32 indexed kind, bytes32 indexed id, address proxy, address impl + ); + + function getAssociatedSystem(bytes32 id) + external + view + returns (address addr, bytes32 kind); + + function initOrUpgradeNft( + bytes32 id, + string memory name, + string memory symbol, + string memory uri, + address impl + ) + external; + + function initOrUpgradeToken( + bytes32 id, + string memory name, + string memory symbol, + uint8 decimals, + address impl + ) + external; + + function registerUnmanagedSystem(bytes32 id, address endpoint) external; + + error AccountActivityTimeoutPending( + uint128 accountId, uint256 currentTime, uint256 requiredTime + ); + error CollateralDepositDisabled(address collateralType); + error CollateralNotFound(); + error FailedTransfer(address from, address to, uint256 value); + error InsufficientAccountCollateral(uint256 amount); + error InsufficientAllowance(uint256 required, uint256 existing); + error InvalidParameter(string parameter, string reason); + error OverflowUint256ToUint64(); + error PrecisionLost(uint256 tokenAmount, uint8 decimals); + + event CollateralLockCreated( + uint128 indexed accountId, + address indexed collateralType, + uint256 tokenAmount, + uint64 expireTimestamp + ); + event CollateralLockExpired( + uint128 indexed accountId, + address indexed collateralType, + uint256 tokenAmount, + uint64 expireTimestamp + ); + event Deposited( + uint128 indexed accountId, + address indexed collateralType, + uint256 tokenAmount, + address indexed sender + ); + event Withdrawn( + uint128 indexed accountId, + address indexed collateralType, + uint256 tokenAmount, + address indexed sender + ); + + function cleanExpiredLocks( + uint128 accountId, + address collateralType, + uint256 offset, + uint256 count + ) + external + returns (uint256 cleared); + + function createLock( + uint128 accountId, + address collateralType, + uint256 amount, + uint64 expireTimestamp + ) + external; + + function deposit( + uint128 accountId, + address collateralType, + uint256 tokenAmount + ) + external; + + function getAccountAvailableCollateral( + uint128 accountId, + address collateralType + ) + external + view + returns (uint256); + + function getAccountCollateral( + uint128 accountId, + address collateralType + ) + external + view + returns ( + uint256 totalDeposited, + uint256 totalAssigned, + uint256 totalLocked + ); + + function getLocks( + uint128 accountId, + address collateralType, + uint256 offset, + uint256 count + ) + external + view + returns (CollateralLock.Data[] memory locks); + + function withdraw( + uint128 accountId, + address collateralType, + uint256 tokenAmount + ) + external; + + event CollateralConfigured( + address indexed collateralType, CollateralConfiguration.Data config + ); + + function configureCollateral(CollateralConfiguration.Data memory config) + external; + + function getCollateralConfiguration(address collateralType) + external + view + returns (CollateralConfiguration.Data memory); + + function getCollateralConfigurations(bool hideDisabled) + external + view + returns (CollateralConfiguration.Data[] memory); + + function getCollateralPrice(address collateralType) + external + view + returns (uint256); + + error InsufficientDebt(int256 currentDebt); + error PoolNotFound(uint128 poolId); + + event IssuanceFeePaid( + uint128 indexed accountId, + uint128 indexed poolId, + address collateralType, + uint256 feeAmount + ); + event UsdBurned( + uint128 indexed accountId, + uint128 indexed poolId, + address collateralType, + uint256 amount, + address indexed sender + ); + event UsdMinted( + uint128 indexed accountId, + uint128 indexed poolId, + address collateralType, + uint256 amount, + address indexed sender + ); + + function burnUsd( + uint128 accountId, + uint128 poolId, + address collateralType, + uint256 amount + ) + external; + + function mintUsd( + uint128 accountId, + uint128 poolId, + address collateralType, + uint256 amount + ) + external; + + error CannotScaleEmptyMapping(); + error IneligibleForLiquidation( + uint256 collateralValue, + int256 debt, + uint256 currentCRatio, + uint256 cratio + ); + error InsufficientMappedAmount(); + error MustBeVaultLiquidated(); + error OverflowInt128ToUint128(); + + event Liquidation( + uint128 indexed accountId, + uint128 indexed poolId, + address indexed collateralType, + ILiquidationModule.LiquidationData liquidationData, + uint128 liquidateAsAccountId, + address sender + ); + event VaultLiquidation( + uint128 indexed poolId, + address indexed collateralType, + ILiquidationModule.LiquidationData liquidationData, + uint128 liquidateAsAccountId, + address sender + ); + + function isPositionLiquidatable( + uint128 accountId, + uint128 poolId, + address collateralType + ) + external + returns (bool); + + function isVaultLiquidatable( + uint128 poolId, + address collateralType + ) + external + returns (bool); + + function liquidate( + uint128 accountId, + uint128 poolId, + address collateralType, + uint128 liquidateAsAccountId + ) + external + returns (ILiquidationModule.LiquidationData memory liquidationData); + + function liquidateVault( + uint128 poolId, + address collateralType, + uint128 liquidateAsAccountId, + uint256 maxUsd + ) + external + returns (ILiquidationModule.LiquidationData memory liquidationData); + + error InsufficientMarketCollateralDepositable( + uint128 marketId, address collateralType, uint256 tokenAmountToDeposit + ); + error InsufficientMarketCollateralWithdrawable( + uint128 marketId, address collateralType, uint256 tokenAmountToWithdraw + ); + + event MarketCollateralDeposited( + uint128 indexed marketId, + address indexed collateralType, + uint256 tokenAmount, + address indexed sender + ); + event MarketCollateralWithdrawn( + uint128 indexed marketId, + address indexed collateralType, + uint256 tokenAmount, + address indexed sender + ); + event MaximumMarketCollateralConfigured( + uint128 indexed marketId, + address indexed collateralType, + uint256 systemAmount, + address indexed owner + ); + + function configureMaximumMarketCollateral( + uint128 marketId, + address collateralType, + uint256 amount + ) + external; + + function depositMarketCollateral( + uint128 marketId, + address collateralType, + uint256 tokenAmount + ) + external; + + function getMarketCollateralAmount( + uint128 marketId, + address collateralType + ) + external + view + returns (uint256 collateralAmountD18); + + function getMarketCollateralValue(uint128 marketId) + external + view + returns (uint256); + + function getMaximumMarketCollateral( + uint128 marketId, + address collateralType + ) + external + view + returns (uint256); + + function withdrawMarketCollateral( + uint128 marketId, + address collateralType, + uint256 tokenAmount + ) + external; + + error IncorrectMarketInterface(address market); + error NotEnoughLiquidity(uint128 marketId, uint256 amount); + + event MarketRegistered( + address indexed market, uint128 indexed marketId, address indexed sender + ); + event MarketSystemFeePaid(uint128 indexed marketId, uint256 feeAmount); + event MarketUsdDeposited( + uint128 indexed marketId, + address indexed target, + uint256 amount, + address indexed market + ); + event MarketUsdWithdrawn( + uint128 indexed marketId, + address indexed target, + uint256 amount, + address indexed market + ); + event SetMarketMinLiquidityRatio( + uint128 indexed marketId, uint256 minLiquidityRatio + ); + event SetMinDelegateTime(uint128 indexed marketId, uint32 minDelegateTime); + + function depositMarketUsd( + uint128 marketId, + address target, + uint256 amount + ) + external + returns (uint256 feeAmount); + + function distributeDebtToPools( + uint128 marketId, + uint256 maxIter + ) + external + returns (bool); + + function getMarketCollateral(uint128 marketId) + external + view + returns (uint256); + + function getMarketDebtPerShare(uint128 marketId) + external + returns (int256); + + function getMarketFees( + uint128, + uint256 amount + ) + external + view + returns (uint256 depositFeeAmount, uint256 withdrawFeeAmount); + + function getMarketMinDelegateTime(uint128 marketId) + external + view + returns (uint32); + + function getMarketNetIssuance(uint128 marketId) + external + view + returns (int128); + + function getMarketReportedDebt(uint128 marketId) + external + view + returns (uint256); + + function getMarketTotalDebt(uint128 marketId) + external + view + returns (int256); + + function getMinLiquidityRatio(uint128 marketId) + external + view + returns (uint256); + + function getOracleManager() external view returns (address); + + function getUsdToken() external view returns (address); + + function getWithdrawableMarketUsd(uint128 marketId) + external + view + returns (uint256); + + function isMarketCapacityLocked(uint128 marketId) + external + view + returns (bool); + + function registerMarket(address market) + external + returns (uint128 marketId); + + function setMarketMinDelegateTime( + uint128 marketId, + uint32 minDelegateTime + ) + external; + + function setMinLiquidityRatio( + uint128 marketId, + uint256 minLiquidityRatio + ) + external; + + function withdrawMarketUsd( + uint128 marketId, + address target, + uint256 amount + ) + external + returns (uint256 feeAmount); + + function multicall(bytes[] memory data) + external + payable + returns (bytes[] memory results); + + event PoolApprovedAdded(uint256 poolId); + event PoolApprovedRemoved(uint256 poolId); + event PreferredPoolSet(uint256 poolId); + + function addApprovedPool(uint128 poolId) external; + + function getApprovedPools() external view returns (uint256[] memory); + + function getPreferredPool() external view returns (uint128); + + function removeApprovedPool(uint128 poolId) external; + + function setPreferredPool(uint128 poolId) external; + + error CapacityLocked(uint256 marketId); + error MinDelegationTimeoutPending(uint128 poolId, uint32 timeRemaining); + error PoolAlreadyExists(uint128 poolId); + + event PoolConfigurationSet( + uint128 indexed poolId, + MarketConfiguration.Data[] markets, + address indexed sender + ); + event PoolCreated( + uint128 indexed poolId, address indexed owner, address indexed sender + ); + event PoolNameUpdated( + uint128 indexed poolId, string name, address indexed sender + ); + event PoolNominationRenounced( + uint128 indexed poolId, address indexed owner + ); + event PoolNominationRevoked(uint128 indexed poolId, address indexed owner); + event PoolOwnerNominated( + uint128 indexed poolId, + address indexed nominatedOwner, + address indexed owner + ); + event PoolOwnershipAccepted(uint128 indexed poolId, address indexed owner); + event SetMinLiquidityRatio(uint256 minLiquidityRatio); + + function acceptPoolOwnership(uint128 poolId) external; + + function createPool(uint128 requestedPoolId, address owner) external; + + function getMinLiquidityRatio() external view returns (uint256); + + function getNominatedPoolOwner(uint128 poolId) + external + view + returns (address); + + function getPoolConfiguration(uint128 poolId) + external + view + returns (MarketConfiguration.Data[] memory); + + function getPoolName(uint128 poolId) + external + view + returns (string memory poolName); + + function getPoolOwner(uint128 poolId) external view returns (address); + + function nominatePoolOwner( + address nominatedOwner, + uint128 poolId + ) + external; + + function renouncePoolNomination(uint128 poolId) external; + + function revokePoolNomination(uint128 poolId) external; + + function setMinLiquidityRatio(uint256 minLiquidityRatio) external; + + function setPoolConfiguration( + uint128 poolId, + MarketConfiguration.Data[] memory newMarketConfigurations + ) + external; + + function setPoolName(uint128 poolId, string memory name) external; + + error OverflowUint256ToUint32(); + error OverflowUint32ToInt32(); + error OverflowUint64ToInt64(); + error RewardDistributorNotFound(); + error RewardUnavailable(address distributor); + + event RewardsClaimed( + uint128 indexed accountId, + uint128 indexed poolId, + address indexed collateralType, + address distributor, + uint256 amount + ); + event RewardsDistributed( + uint128 indexed poolId, + address indexed collateralType, + address distributor, + uint256 amount, + uint256 start, + uint256 duration + ); + event RewardsDistributorRegistered( + uint128 indexed poolId, + address indexed collateralType, + address indexed distributor + ); + event RewardsDistributorRemoved( + uint128 indexed poolId, + address indexed collateralType, + address indexed distributor + ); + + function claimRewards( + uint128 accountId, + uint128 poolId, + address collateralType, + address distributor + ) + external + returns (uint256); + + function distributeRewards( + uint128 poolId, + address collateralType, + uint256 amount, + uint64 start, + uint32 duration + ) + external; + + function getRewardRate( + uint128 poolId, + address collateralType, + address distributor + ) + external + view + returns (uint256); + + function registerRewardsDistributor( + uint128 poolId, + address collateralType, + address distributor + ) + external; + + function removeRewardsDistributor( + uint128 poolId, + address collateralType, + address distributor + ) + external; + + function updateRewards( + uint128 poolId, + address collateralType, + uint128 accountId + ) + external + returns (uint256[] memory, address[] memory); + + function configureOracleManager(address oracleManagerAddress) external; + + function getConfig(bytes32 k) external view returns (bytes32 v); + + function registerCcip( + address ccipSend, + address ccipReceive, + address ccipTokenPool + ) + external; + + function setConfig(bytes32 k, bytes32 v) external; + + error InsufficientDelegation(uint256 minDelegation); + error InvalidCollateralAmount(); + error InvalidLeverage(uint256 leverage); + + event DelegationUpdated( + uint128 indexed accountId, + uint128 indexed poolId, + address collateralType, + uint256 amount, + uint256 leverage, + address indexed sender + ); + + function delegateCollateral( + uint128 accountId, + uint128 poolId, + address collateralType, + uint256 newCollateralAmountD18, + uint256 leverage + ) + external; + + function getPosition( + uint128 accountId, + uint128 poolId, + address collateralType + ) + external + returns ( + uint256 collateralAmount, + uint256 collateralValue, + int256 debt, + uint256 collateralizationRatio + ); + + function getPositionCollateral( + uint128 accountId, + uint128 poolId, + address collateralType + ) + external + view + returns (uint256 amount, uint256 value); + + function getPositionCollateralRatio( + uint128 accountId, + uint128 poolId, + address collateralType + ) + external + returns (uint256); + + function getPositionDebt( + uint128 accountId, + uint128 poolId, + address collateralType + ) + external + returns (int256); + + function getVaultCollateral( + uint128 poolId, + address collateralType + ) + external + view + returns (uint256 amount, uint256 value); + + function getVaultCollateralRatio( + uint128 poolId, + address collateralType + ) + external + returns (uint256); + + function getVaultDebt( + uint128 poolId, + address collateralType + ) + external + returns (int256); + +} + +interface IAccountModule { + + struct AccountPermissions { + address user; + bytes32[] permissions; + } + +} + +interface CollateralLock { + + struct Data { + uint128 amountD18; + uint64 lockExpirationTime; + } + +} + +interface CollateralConfiguration { + + struct Data { + bool depositingEnabled; + uint256 issuanceRatioD18; + uint256 liquidationRatioD18; + uint256 liquidationRewardD18; + bytes32 oracleNodeId; + address tokenAddress; + uint256 minDelegationD18; + } + +} + +interface ILiquidationModule { + + struct LiquidationData { + uint256 debtLiquidated; + uint256 collateralLiquidated; + uint256 amountRewarded; + } + +} + +interface MarketConfiguration { + + struct Data { + uint128 marketId; + uint128 weightD18; + int128 maxDebtShareValueD18; + } + +} diff --git a/src/interfaces/IUniswap.sol b/src/interfaces/IUniswap.sol new file mode 100644 index 0000000..8dd44ae --- /dev/null +++ b/src/interfaces/IUniswap.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +interface IUniswap { + + struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; + } + + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + returns (uint256 amountOut); + +} diff --git a/test/Unwrap.t.sol b/test/Unwrap.t.sol deleted file mode 100644 index ca11794..0000000 --- a/test/Unwrap.t.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -import {Zap} from "../src/Zap.sol"; -import {ZapErrors} from "../src/ZapErrors.sol"; -import {ZapEvents} from "../src/ZapEvents.sol"; - -import {MockERC20} from "./utils/mocks/MockERC20.sol"; -import {MockSpotMarketProxy} from "./utils/mocks/MockSpotMarketProxy.sol"; -import {Test} from "forge-std/Test.sol"; - -contract UnwrapTest is Test, ZapEvents { - - Zap public zap; - MockERC20 public collateral; - MockERC20 public synth; - MockSpotMarketProxy public spotMarket; - - address public constant RECEIVER = address(0x123); - address public constant REFERRER = address(0x456); - uint128 public constant MARKET_ID = 1; - uint256 public constant INITIAL_BALANCE = 1000e18; - - function setUp() public { - zap = new Zap(); - collateral = new MockERC20("Collateral", "COL", 18); - synth = new MockERC20("Synth", "SYN", 18); - spotMarket = new MockSpotMarketProxy(); - - // Set the synth address in the MockSpotMarketProxy - spotMarket.setSynthAddress(MARKET_ID, address(synth)); - - // Setup initial balances and approvals - synth.mint(address(this), INITIAL_BALANCE); - synth.approve(address(zap), type(uint256).max); - - // Mint collateral to the Zap contract instead of the SpotMarketProxy - collateral.mint(address(zap), INITIAL_BALANCE); - } - - function test_unwrap_success() public { - uint256 amount = 100e18; - uint256 expectedOutput = amount; // Assuming 1:1 ratio for simplicity - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: 0 // Not used for unwrap - }), - direction: Zap.Direction.Out, // Not used for unwrap - receiver: RECEIVER, - referrer: REFERRER // Not used for unwrap - }); - - vm.expectEmit(true, true, true, true); - emit Unwrapped( - address(this), MARKET_ID, amount, expectedOutput, RECEIVER - ); - - zap.unwrap(zapData); - - // Assert the final state - assertEq(collateral.balanceOf(RECEIVER), expectedOutput); - } - - function test_unwrap_failed() public { - uint256 amount = 100e18; - - // Setup to make unwrap fail - spotMarket.setUnwrapShouldRevert(true); - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: 0 // Not used for unwrap - }), - direction: Zap.Direction.Out, // Not used for unwrap - receiver: RECEIVER, - referrer: REFERRER // Not used for unwrap - }); - - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.UnwrapFailed.selector, "Unwrap failed" - ) - ); - zap.unwrap(zapData); - } - -} diff --git a/test/Wrap.t.sol b/test/Wrap.t.sol deleted file mode 100644 index f105c58..0000000 --- a/test/Wrap.t.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -import {Zap} from "../src/Zap.sol"; -import {ZapErrors} from "../src/ZapErrors.sol"; -import {ZapEvents} from "../src/ZapEvents.sol"; - -import {MockERC20} from "./utils/mocks/MockERC20.sol"; -import {MockSpotMarketProxy} from "./utils/mocks/MockSpotMarketProxy.sol"; -import {Test} from "forge-std/Test.sol"; - -contract WrapTest is Test, ZapEvents { - - Zap public zap; - MockERC20 public collateral; - MockERC20 public synth; - MockSpotMarketProxy public spotMarket; - - address public constant RECEIVER = address(0x123); - address public constant REFERRER = address(0x456); - uint128 public constant MARKET_ID = 1; - uint256 public constant INITIAL_BALANCE = 1000e18; - - function setUp() public { - zap = new Zap(); - collateral = new MockERC20("Collateral", "COL", 18); - synth = new MockERC20("Synth", "SYN", 18); - spotMarket = new MockSpotMarketProxy(); - - // Set the synth address in the MockSpotMarketProxy - spotMarket.setSynthAddress(MARKET_ID, address(synth)); - - // Setup initial balances and approvals - collateral.mint(address(this), INITIAL_BALANCE); - collateral.approve(address(zap), type(uint256).max); - - // Mint synth tokens to the MockSpotMarketProxy for it to transfer - // during 'wrap' - synth.mint(address(zap), INITIAL_BALANCE); - } - - function test_wrap_success() public { - uint256 amount = 100e18; - uint256 expectedOutput = amount; // Assuming 1:1 ratio for simplicity - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: 0 // Not used for wrap - }), - direction: Zap.Direction.In, // Not used for wrap - receiver: RECEIVER, - referrer: REFERRER // Not used for wrap - }); - - vm.expectEmit(true, true, true, true); - emit Wrapped(address(this), MARKET_ID, amount, expectedOutput, RECEIVER); - - zap.wrap(zapData); - - // Assert the final state - assertEq(synth.balanceOf(RECEIVER), expectedOutput); - } - - function test_wrap_failed() public { - uint256 amount = 100e18; - - // Setup to make wrap fail - spotMarket.setWrapShouldRevert(true); - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: 0 // Not used for wrap - }), - direction: Zap.Direction.In, // Not used for wrap - receiver: RECEIVER, - referrer: REFERRER // Not used for wrap - }); - - vm.expectRevert( - abi.encodeWithSelector(ZapErrors.WrapFailed.selector, "Wrap failed") - ); - zap.wrap(zapData); - } - -} diff --git a/test/Zap.In.t.sol b/test/Zap.In.t.sol deleted file mode 100644 index b91be96..0000000 --- a/test/Zap.In.t.sol +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -import {Zap} from "../src/Zap.sol"; -import {ZapErrors} from "../src/ZapErrors.sol"; -import {ZapEvents} from "../src/ZapEvents.sol"; - -import {MockERC20} from "./utils/mocks/MockERC20.sol"; -import {MockSpotMarketProxy} from "./utils/mocks/MockSpotMarketProxy.sol"; -import {Test} from "forge-std/Test.sol"; - -contract ZapInTest is Test, ZapEvents { - - Zap public zap; - MockERC20 public collateral; - MockERC20 public synth; - MockERC20 public sUSD; - MockSpotMarketProxy public spotMarket; - - address public constant RECEIVER = address(0x123); - address public constant REFERRER = address(0x456); - uint128 public constant MARKET_ID = 1; - uint128 public constant USD_SPOT_MARKET_ID = 0; - uint256 public constant INITIAL_BALANCE = 1000e18; - - function setUp() public { - zap = new Zap(); - collateral = new MockERC20("Collateral", "COL", 18); - synth = new MockERC20("Synth", "SYN", 18); - sUSD = new MockERC20("sUSD", "sUSD", 18); - spotMarket = new MockSpotMarketProxy(); - - // Set the synth address in the MockSpotMarketProxy - spotMarket.setSynthAddress(MARKET_ID, address(synth)); - spotMarket.setSynthAddress(USD_SPOT_MARKET_ID, address(sUSD)); - - // Setup initial balances and approvals - collateral.mint(address(this), INITIAL_BALANCE); - collateral.approve(address(zap), type(uint256).max); - - synth.mint(address(zap), INITIAL_BALANCE); - sUSD.mint(address(zap), INITIAL_BALANCE); - } - - function test_zapIn_success() public { - uint256 amount = 100e18; - uint256 expectedOutput = amount; - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: expectedOutput * 95 / 100 - }), - direction: Zap.Direction.In, - receiver: RECEIVER, - referrer: REFERRER - }); - - vm.expectEmit(true, true, true, true); - emit ZapIn(address(this), MARKET_ID, amount, expectedOutput, RECEIVER); - - zap.zap(zapData); - - // Assert the final state - assertEq(collateral.balanceOf(address(this)), INITIAL_BALANCE - amount); - assertEq(sUSD.balanceOf(RECEIVER), expectedOutput); - } - - function test_zapIn_wrapFailed() public { - uint256 amount = 100e18; - - // Setup to make wrap fail - spotMarket.setWrapShouldRevert(true); - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: amount * 95 / 100 - }), - direction: Zap.Direction.In, - receiver: RECEIVER, - referrer: REFERRER - }); - - vm.expectRevert( - abi.encodeWithSelector(ZapErrors.WrapFailed.selector, "Wrap failed") - ); - zap.zap(zapData); - } - - function test_zapIn_swapFailed() public { - uint256 amount = 100e18; - - // Setup to make sell fail - spotMarket.setSellShouldRevert(true); - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: amount * 95 / 100 - }), - direction: Zap.Direction.In, - receiver: RECEIVER, - referrer: REFERRER - }); - - vm.expectRevert( - abi.encodeWithSelector(ZapErrors.SwapFailed.selector, "Sell failed") - ); - zap.zap(zapData); - } - -} diff --git a/test/Zap.Out.t.sol b/test/Zap.Out.t.sol deleted file mode 100644 index 89c30b2..0000000 --- a/test/Zap.Out.t.sol +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -import {Zap} from "../src/Zap.sol"; -import {ZapErrors} from "../src/ZapErrors.sol"; -import {ZapEvents} from "../src/ZapEvents.sol"; - -import {MockERC20} from "./utils/mocks/MockERC20.sol"; -import {MockSpotMarketProxy} from "./utils/mocks/MockSpotMarketProxy.sol"; -import {Test} from "forge-std/Test.sol"; - -contract ZapOutTest is Test, ZapEvents { - - Zap public zap; - MockERC20 public collateral; - MockERC20 public synth; - MockERC20 public sUSD; - MockSpotMarketProxy public spotMarket; - - address public constant RECEIVER = address(0x123); - address public constant REFERRER = address(0x456); - uint128 public constant MARKET_ID = 1; - uint128 public constant USD_SPOT_MARKET_ID = 0; - uint256 public constant INITIAL_BALANCE = 1000e18; - - function setUp() public { - zap = new Zap(); - collateral = new MockERC20("Collateral", "COL", 18); - synth = new MockERC20("Synth", "SYN", 18); - sUSD = new MockERC20("sUSD", "sUSD", 18); - spotMarket = new MockSpotMarketProxy(); - - // Set the synth address in the MockSpotMarketProxy - spotMarket.setSynthAddress(MARKET_ID, address(synth)); - spotMarket.setSynthAddress(USD_SPOT_MARKET_ID, address(sUSD)); - - // Setup initial balances and approvals - sUSD.mint(address(this), INITIAL_BALANCE); - sUSD.approve(address(zap), type(uint256).max); - - synth.mint(address(zap), INITIAL_BALANCE); - collateral.mint(address(zap), INITIAL_BALANCE); - } - - function test_zapOut_success() public { - uint256 amount = 100e18; - uint256 expectedOutput = amount; // Assuming 1:1 ratio for simplicity - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: expectedOutput * 95 / 100, - tolerableSwapAmount: amount * 95 / 100 - }), - direction: Zap.Direction.Out, - receiver: RECEIVER, - referrer: REFERRER - }); - - vm.expectEmit(true, true, true, true); - emit ZapOut(address(this), MARKET_ID, amount, expectedOutput, RECEIVER); - - zap.zap(zapData); - - // Assert the final state - assertEq(sUSD.balanceOf(address(this)), INITIAL_BALANCE - amount); - assertEq(collateral.balanceOf(RECEIVER), expectedOutput); - } - - function test_zapOut_swapFailed() public { - uint256 amount = 100e18; - - // Setup to make buy fail - spotMarket.setBuyShouldRevert(true); - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: amount * 95 / 100 - }), - direction: Zap.Direction.Out, - receiver: RECEIVER, - referrer: REFERRER - }); - - vm.expectRevert( - abi.encodeWithSelector(ZapErrors.SwapFailed.selector, "Buy failed") - ); - zap.zap(zapData); - } - - function test_zapOut_unwrapFailed() public { - uint256 amount = 100e18; - - // Setup to make unwrap fail - spotMarket.setUnwrapShouldRevert(true); - - Zap.ZapData memory zapData = Zap.ZapData({ - spotMarket: spotMarket, - collateral: collateral, - marketId: MARKET_ID, - amount: amount, - tolerance: Zap.Tolerance({ - tolerableWrapAmount: amount * 95 / 100, - tolerableSwapAmount: amount * 95 / 100 - }), - direction: Zap.Direction.Out, - receiver: RECEIVER, - referrer: REFERRER - }); - - vm.expectRevert( - abi.encodeWithSelector( - ZapErrors.UnwrapFailed.selector, "Unwrap failed" - ) - ); - zap.zap(zapData); - } - -} diff --git a/test/Zap.t.sol b/test/Zap.t.sol new file mode 100644 index 0000000..f1decbb --- /dev/null +++ b/test/Zap.t.sol @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Arbitrum, Base} from "../script/utils/Parameters.sol"; +import { + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Zap +} from "../src/Zap.sol"; +import {Test} from "forge-std/Test.sol"; + +contract ZapTest is Test, Arbitrum, Base { + + uint256 BASE; + uint256 ARBITRUM; + Zap zap; + ISpotMarket spotMarket; + IPerpsMarket perpsMarket; + IERC20 usdc; + IERC20 susdc; + IERC20 usdx; + IERC20 weth; + address actor = 0x7777777777777777777777777777777777777777; + uint256 tolerance = 0; + + function setUp() public { + string memory BASE_RPC = vm.envString("BASE_RPC"); + string memory ARB_RPC = vm.envString("ARBITRUM_RPC"); + BASE = vm.createFork(BASE_RPC, 20_165_000); + ARBITRUM = vm.createFork(ARB_RPC, 256_615_000); + } + + modifier base() { + vm.selectFork(BASE); + zap = new Zap({ + _usdc: BASE_USDC, + _usdx: BASE_USDX, + _spotMarket: BASE_SPOT_MARKET, + _perpsMarket: BASE_PERPS_MARKET, + _core: BASE_CORE, + _referrer: BASE_REFERRER, + _susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, + _aave: BASE_AAVE_POOL, + _uniswap: BASE_UNISWAP + }); + spotMarket = ISpotMarket(BASE_SPOT_MARKET); + perpsMarket = IPerpsMarket(BASE_PERPS_MARKET); + usdc = IERC20(BASE_USDC); + susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); + usdx = IERC20(BASE_USDX); + weth = IERC20(0x4200000000000000000000000000000000000006); + _; + } + + modifier arbitrum() { + vm.selectFork(ARBITRUM); + zap = new Zap({ + _usdc: ARBITRUM_USDC, + _usdx: ARBITRUM_USDX, + _spotMarket: ARBITRUM_SPOT_MARKET, + _perpsMarket: ARBITRUM_PERPS_MARKET, + _core: ARBITRUM_CORE, + _referrer: ARBITRUM_REFERRER, + _susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, + _aave: ARBITRUM_AAVE_POOL, + _uniswap: ARBITRUM_UNISWAP + }); + spotMarket = ISpotMarket(ARBITRUM_SPOT_MARKET); + perpsMarket = IPerpsMarket(ARBITRUM_PERPS_MARKET); + usdc = IERC20(ARBITRUM_USDC); + susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); + usdx = IERC20(ARBITRUM_USDX); + weth = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1); + _; + } + + function _spin( + address addr, + IERC20 token, + uint256 amount, + address approved + ) + internal + { + vm.assume(amount > 1e6); + deal(address(token), addr, amount); + vm.prank(addr); + IERC20(token).approve(approved, type(uint256).max); + } + + function test_zap_in_arbitrum(uint32 amount) public arbitrum { + _spin(actor, usdc, amount, address(zap)); + assertEq(usdc.balanceOf(actor), amount); + assertEq(usdx.balanceOf(actor), 0); + vm.startPrank(actor); + uint256 zapped = zap.zapIn({ + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(zapped, tolerance); + assertEq(usdc.balanceOf(actor), 0); + assertEq(usdx.balanceOf(actor), zapped); + } + + function test_zap_in_base(uint32 amount) public base { + _spin(actor, usdc, amount, address(zap)); + assertEq(usdc.balanceOf(actor), amount); + assertEq(usdx.balanceOf(actor), 0); + vm.startPrank(actor); + uint256 zapped = zap.zapIn({ + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(zapped, tolerance); + assertEq(usdc.balanceOf(actor), 0); + assertEq(usdx.balanceOf(actor), zapped); + } + + function test_zap_out_arbitum(uint32 amount) public arbitrum { + _spin(actor, usdx, amount, address(zap)); + assertEq(usdc.balanceOf(actor), 0); + assertEq(usdx.balanceOf(actor), amount); + vm.startPrank(actor); + uint256 zapped = zap.zapOut({ + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(zapped, tolerance); + assertEq(usdc.balanceOf(actor), zapped); + assertEq(usdx.balanceOf(actor), 0); + } + + function test_zap_out_base(uint32 amount) public base { + _spin(actor, usdx, amount, address(zap)); + assertEq(usdc.balanceOf(actor), 0); + assertEq(usdx.balanceOf(actor), amount); + vm.startPrank(actor); + uint256 zapped = zap.zapOut({ + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(zapped, tolerance); + assertEq(usdc.balanceOf(actor), zapped); + assertEq(usdx.balanceOf(actor), 0); + } + + function test_wrap_arbitrum(uint32 amount) public arbitrum { + _spin(actor, usdc, amount, address(zap)); + assertEq(usdc.balanceOf(actor), amount); + assertEq(susdc.balanceOf(actor), 0); + vm.startPrank(actor); + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(wrapped, tolerance); + assertEq(usdc.balanceOf(actor), 0); + assertEq(susdc.balanceOf(actor), wrapped); + } + + function test_wrap_base(uint32 amount) public base { + _spin(actor, usdc, amount, address(zap)); + assertEq(usdc.balanceOf(actor), amount); + assertEq(susdc.balanceOf(actor), 0); + vm.startPrank(actor); + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(wrapped, tolerance); + assertEq(usdc.balanceOf(actor), 0); + assertEq(susdc.balanceOf(actor), wrapped); + } + + function test_unwrap_arbitrum(uint32 amount) public arbitrum { + _spin(actor, usdc, amount, address(zap)); + vm.startPrank(actor); + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + assertEq(usdc.balanceOf(actor), 0); + assertGe(susdc.balanceOf(actor), tolerance); + susdc.approve(address(zap), type(uint256).max); + uint256 unwrapped = zap.unwrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: wrapped, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(unwrapped, tolerance); + assertEq(usdc.balanceOf(actor), amount); + assertEq(susdc.balanceOf(actor), 0); + } + + function test_unwrap_base(uint32 amount) public base { + _spin(actor, usdc, amount, address(zap)); + vm.startPrank(actor); + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + assertEq(usdc.balanceOf(actor), 0); + assertGe(susdc.balanceOf(actor), tolerance); + susdc.approve(address(zap), type(uint256).max); + uint256 unwrapped = zap.unwrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: wrapped, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(unwrapped, tolerance); + assertEq(usdc.balanceOf(actor), amount); + assertEq(susdc.balanceOf(actor), 0); + } + + function test_buy_arbitrum(uint32 amount) public arbitrum { + _spin(actor, usdx, amount, address(zap)); + assertEq(usdx.balanceOf(actor), amount); + assertEq(susdc.balanceOf(actor), 0); + vm.startPrank(actor); + (uint256 received, address synth) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertEq(synth, address(susdc)); + assertGe(received, tolerance); + assertEq(usdx.balanceOf(actor), 0); + assertGe(susdc.balanceOf(actor), tolerance); + } + + function test_sell_arbitrum(uint32 amount) public arbitrum { + _spin(actor, usdx, amount, address(zap)); + vm.startPrank(actor); + (uint256 received,) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + assertEq(usdx.balanceOf(actor), 0); + assertGe(susdc.balanceOf(actor), tolerance); + susdc.approve(address(zap), type(uint256).max); + received = zap.sell({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: received, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(received, tolerance); + assertGe(usdx.balanceOf(actor), tolerance); + assertEq(susdc.balanceOf(actor), 0); + } + + function test_sell_base(uint32 amount) public base { + _spin(actor, usdx, amount, address(zap)); + vm.startPrank(actor); + (uint256 received,) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + assertEq(usdx.balanceOf(actor), 0); + assertGe(susdc.balanceOf(actor), tolerance); + susdc.approve(address(zap), type(uint256).max); + received = zap.sell({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: received, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertGe(received, tolerance); + assertGe(usdx.balanceOf(actor), tolerance); + assertEq(susdc.balanceOf(actor), 0); + } + + function test_swap_arbitrum() public arbitrum { + uint256 amount = 1 ether; + _spin(actor, weth, amount, address(zap)); + assertEq(weth.balanceOf(actor), amount); + assertEq(usdc.balanceOf(actor), 0); + vm.startPrank(actor); + uint256 received = zap.swap({ + _from: address(weth), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertEq(weth.balanceOf(actor), 0); + assertEq(usdc.balanceOf(actor), received); + assertTrue(received > 0); + } + + function test_swap_base() public arbitrum { + uint256 amount = 1 ether; + _spin(actor, weth, amount, address(zap)); + assertEq(weth.balanceOf(actor), amount); + assertEq(usdc.balanceOf(actor), 0); + vm.startPrank(actor); + uint256 received = zap.swap({ + _from: address(weth), + _amount: amount, + _tolerance: tolerance, + _receiver: actor + }); + vm.stopPrank(); + assertEq(weth.balanceOf(actor), 0); + assertEq(usdc.balanceOf(actor), received); + assertTrue(received > 0); + } + + function test_flashloan() public arbitrum { + /// @custom:todo + } + +} diff --git a/test/utils/errors/SynthetixV3Errors.sol b/test/utils/errors/SynthetixV3Errors.sol index 4a64d36..88bdc80 100644 --- a/test/utils/errors/SynthetixV3Errors.sol +++ b/test/utils/errors/SynthetixV3Errors.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; /// @title Cosolidated Errors from Synthetix v3 contracts /// @author JaredBorders (jaredborders@pm.me) diff --git a/test/utils/mocks/MockERC20.sol b/test/utils/mocks/MockERC20.sol deleted file mode 100644 index bd75508..0000000 --- a/test/utils/mocks/MockERC20.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -import {IERC20} from "./../../../src/interfaces/IERC20.sol"; - -/// @title MockERC20 contract for testing -/// @dev Implements ERC20 standard with extra state modification functions -/// @author JaredBorders (jaredborders@pm.me) -contract MockERC20 is IERC20 { - - string public name; - string public symbol; - uint8 public decimals; - uint256 public totalSupply; - mapping(address => uint256) public balanceOf; - mapping(address => mapping(address => uint256)) public allowance; - - constructor(string memory _name, string memory _symbol, uint8 _decimals) { - name = _name; - symbol = _symbol; - decimals = _decimals; - } - - function approve( - address spender, - uint256 amount - ) - public - override - returns (bool) - { - allowance[msg.sender][spender] = amount; - return true; - } - - function transfer( - address to, - uint256 amount - ) - public - override - returns (bool) - { - balanceOf[msg.sender] -= amount; - balanceOf[to] += amount; - return true; - } - - function transferFrom( - address from, - address to, - uint256 amount - ) - public - override - returns (bool) - { - uint256 allowed = allowance[from][msg.sender]; - if (allowed != type(uint256).max) { - allowance[from][msg.sender] = allowed - amount; - } - balanceOf[from] -= amount; - balanceOf[to] += amount; - return true; - } - - function mint(address to, uint256 amount) public { - totalSupply += amount; - balanceOf[to] += amount; - } - - function burn(address from, uint256 amount) public { - balanceOf[from] -= amount; - totalSupply -= amount; - } - - function setAllowance( - address owner, - address spender, - uint256 amount - ) - public - { - allowance[owner][spender] = amount; - } - -} diff --git a/test/utils/mocks/MockSpotMarketProxy.sol b/test/utils/mocks/MockSpotMarketProxy.sol deleted file mode 100644 index 003fc39..0000000 --- a/test/utils/mocks/MockSpotMarketProxy.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.26; - -import {ISpotMarketProxy} from "./../../../src/interfaces/ISpotMarketProxy.sol"; - -/// @title MockSpotMarketProxy contract for testing -/// @author JaredBorders (jaredborders@pm.me) -contract MockSpotMarketProxy is ISpotMarketProxy { - - bool public wrapShouldRevert; - bool public unwrapShouldRevert; - bool public sellShouldRevert; - bool public buyShouldRevert; - - mapping(uint128 => address) public synthAddresses; - - function setWrapShouldRevert(bool _shouldRevert) external { - wrapShouldRevert = _shouldRevert; - } - - function setUnwrapShouldRevert(bool _shouldRevert) external { - unwrapShouldRevert = _shouldRevert; - } - - function setBuyShouldRevert(bool _shouldRevert) external { - buyShouldRevert = _shouldRevert; - } - - function setSellShouldRevert(bool _shouldRevert) external { - sellShouldRevert = _shouldRevert; - } - - function setSynthAddress(uint128 marketId, address synthAddress) external { - synthAddresses[marketId] = synthAddress; - } - - function name(uint128 /* marketId */ ) - external - pure - override - returns (string memory) - { - return "MockName"; - } - - function getSynth(uint128 marketId) - external - view - override - returns (address) - { - return synthAddresses[marketId]; - } - - function wrap( - uint128, /* marketId */ - uint256 wrapAmount, - uint256 /* minAmountReceived */ - ) - external - view - override - returns (uint256 amountToMint, Data memory fees) - { - require(!wrapShouldRevert, "Wrap failed"); - return (wrapAmount, Data(0, 0, 0, 0)); - } - - function unwrap( - uint128, /* marketId */ - uint256 unwrapAmount, - uint256 /* minAmountReceived */ - ) - external - view - override - returns (uint256 returnCollateralAmount, Data memory fees) - { - require(!unwrapShouldRevert, "Unwrap failed"); - return (unwrapAmount, Data(0, 0, 0, 0)); - } - - function buy( - uint128, /* marketId */ - uint256 usdAmount, - uint256, /* minAmountReceived */ - address /* referrer */ - ) - external - view - override - returns (uint256 synthAmount, Data memory fees) - { - require(!buyShouldRevert, "Buy failed"); - return (usdAmount, Data(0, 0, 0, 0)); - } - - function sell( - uint128, /* marketId */ - uint256 synthAmount, - uint256, /* minAmountReceived */ - address /* referrer */ - ) - external - view - override - returns (uint256 usdAmountReceived, Data memory fees) - { - require(!sellShouldRevert, "Sell failed"); - return (synthAmount, Data(0, 0, 0, 0)); - } - -} From 9e2c41fccbe1ad9d1aa6a6086eedbf8b1564b717 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 01:46:03 -0400 Subject: [PATCH 024/129] =?UTF-8?q?=E2=9C=85=20increase=20logic=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Burn.t.sol | 27 ++ test/Buy.t.sol | 54 ++++ test/Flash.t.sol | 37 +++ test/Sell.t.sol | 66 +++++ test/Swap.t.sol | 54 ++++ test/Unwrap.t.sol | 70 +++++ test/Withdraw.t.sol | 62 +++++ test/Wrap.t.sol | 54 ++++ test/Zap.in.t.sol | 49 ++++ test/Zap.out.t.sol | 50 ++++ test/Zap.t.sol | 352 ------------------------ test/utils/Bootstrap.sol | 100 +++++++ test/utils/Constants.sol | 37 +++ test/utils/errors/SynthetixV3Errors.sol | 112 ++++++++ 14 files changed, 772 insertions(+), 352 deletions(-) create mode 100644 test/Burn.t.sol create mode 100644 test/Buy.t.sol create mode 100644 test/Flash.t.sol create mode 100644 test/Sell.t.sol create mode 100644 test/Swap.t.sol create mode 100644 test/Unwrap.t.sol create mode 100644 test/Withdraw.t.sol create mode 100644 test/Wrap.t.sol create mode 100644 test/Zap.in.t.sol create mode 100644 test/Zap.out.t.sol delete mode 100644 test/Zap.t.sol create mode 100644 test/utils/Bootstrap.sol create mode 100644 test/utils/Constants.sol diff --git a/test/Burn.t.sol b/test/Burn.t.sol new file mode 100644 index 0000000..bb4f9e8 --- /dev/null +++ b/test/Burn.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract BurnTest is Bootstrap { + + function test_burn_arbitrum(uint32 amount) public arbitrum { + vm.rollFork(ARBITRUM_FORK_BLOCK_DEBT); + _spin(ARBITRUM_BOB, usdx, amount, address(zap)); + vm.startPrank(ARBITRUM_BOB); + core.grantPermission(ARBITRUM_BOB_ID, _BURN_PERMISSION, address(zap)); + zap.burn({_amount: amount, _accountId: ARBITRUM_BOB_ID}); + vm.stopPrank(); + } + +} diff --git a/test/Buy.t.sol b/test/Buy.t.sol new file mode 100644 index 0000000..bc05364 --- /dev/null +++ b/test/Buy.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract BuyTest is Bootstrap { + + function test_buy_base(uint32 amount) public base { + _spin(ACTOR, usdx, amount, address(zap)); + assertEq(usdx.balanceOf(ACTOR), amount); + assertEq(susdc.balanceOf(ACTOR), 0); + vm.startPrank(ACTOR); + (uint256 received, address synth) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertEq(synth, address(susdc)); + assertGe(received, DEFAULT_TOLERANCE); + assertEq(usdx.balanceOf(ACTOR), 0); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + } + + function test_buy_arbitrum(uint32 amount) public arbitrum { + _spin(ACTOR, usdx, amount, address(zap)); + assertEq(usdx.balanceOf(ACTOR), amount); + assertEq(susdc.balanceOf(ACTOR), 0); + vm.startPrank(ACTOR); + (uint256 received, address synth) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertEq(synth, address(susdc)); + assertGe(received, DEFAULT_TOLERANCE); + assertEq(usdx.balanceOf(ACTOR), 0); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + } + +} diff --git a/test/Flash.t.sol b/test/Flash.t.sol new file mode 100644 index 0000000..8b4af94 --- /dev/null +++ b/test/Flash.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract FlashTest is Bootstrap { + + /// @custom:todo + function test_flash_arbitrum(uint32 amount) public arbitrum { + vm.rollFork(ARBITRUM_FORK_BLOCK_DEBT); + _spin(ARBITRUM_BOB, usdx, amount, address(zap)); + vm.startPrank(ARBITRUM_BOB); + + // 1. create perp market account + + // 2. approve usdx to perp market + + // 3. grant zap permission to modify collateral + + // 4. modify collateral; add margin + + // 5. request flash loan + + vm.stopPrank(); + } + +} diff --git a/test/Sell.t.sol b/test/Sell.t.sol new file mode 100644 index 0000000..cf83475 --- /dev/null +++ b/test/Sell.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract SellTest is Bootstrap { + + function test_sell_base(uint32 amount) public base { + _spin(ACTOR, usdx, amount, address(zap)); + vm.startPrank(ACTOR); + (uint256 received,) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + assertEq(usdx.balanceOf(ACTOR), 0); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + susdc.approve(address(zap), type(uint256).max); + received = zap.sell({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: received, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(received, DEFAULT_TOLERANCE); + assertGe(usdx.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertEq(susdc.balanceOf(ACTOR), 0); + } + + function test_sell_arbitrum(uint32 amount) public arbitrum { + _spin(ACTOR, usdx, amount, address(zap)); + vm.startPrank(ACTOR); + (uint256 received,) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + assertEq(usdx.balanceOf(ACTOR), 0); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + susdc.approve(address(zap), type(uint256).max); + received = zap.sell({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: received, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(received, DEFAULT_TOLERANCE); + assertGe(usdx.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertEq(susdc.balanceOf(ACTOR), 0); + } + +} diff --git a/test/Swap.t.sol b/test/Swap.t.sol new file mode 100644 index 0000000..0cd2a86 --- /dev/null +++ b/test/Swap.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract SwapTest is Bootstrap { + + function test_swap_base() public base { + uint256 amount = 1 ether; + _spin(ACTOR, weth, amount, address(zap)); + assertEq(weth.balanceOf(ACTOR), amount); + assertEq(usdc.balanceOf(ACTOR), 0); + vm.startPrank(ACTOR); + uint256 received = zap.swap({ + _from: address(weth), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertEq(weth.balanceOf(ACTOR), 0); + assertEq(usdc.balanceOf(ACTOR), received); + assertTrue(received > 0); + } + + function test_swap_arbitrum() public arbitrum { + uint256 amount = 1 ether; + _spin(ACTOR, weth, amount, address(zap)); + assertEq(weth.balanceOf(ACTOR), amount); + assertEq(usdc.balanceOf(ACTOR), 0); + vm.startPrank(ACTOR); + uint256 received = zap.swap({ + _from: address(weth), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertEq(weth.balanceOf(ACTOR), 0); + assertEq(usdc.balanceOf(ACTOR), received); + assertTrue(received > 0); + } + +} diff --git a/test/Unwrap.t.sol b/test/Unwrap.t.sol new file mode 100644 index 0000000..9c13099 --- /dev/null +++ b/test/Unwrap.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract UnwrapTest is Bootstrap { + + function test_unwrap_base(uint32 amount) public base { + _spin(ACTOR, usdc, amount, address(zap)); + vm.startPrank(ACTOR); + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + assertEq(usdc.balanceOf(ACTOR), 0); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + susdc.approve(address(zap), type(uint256).max); + uint256 unwrapped = zap.unwrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: wrapped, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(unwrapped, DEFAULT_TOLERANCE); + assertEq(usdc.balanceOf(ACTOR), amount); + assertEq(susdc.balanceOf(ACTOR), 0); + } + + function test_unwrap_arbitrum(uint32 amount) public arbitrum { + _spin(ACTOR, usdc, amount, address(zap)); + vm.startPrank(ACTOR); + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + assertEq(usdc.balanceOf(ACTOR), 0); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + susdc.approve(address(zap), type(uint256).max); + uint256 unwrapped = zap.unwrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: wrapped, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(unwrapped, DEFAULT_TOLERANCE); + assertEq(usdc.balanceOf(ACTOR), amount); + assertEq(susdc.balanceOf(ACTOR), 0); + } + +} diff --git a/test/Withdraw.t.sol b/test/Withdraw.t.sol new file mode 100644 index 0000000..9bd9570 --- /dev/null +++ b/test/Withdraw.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract WithdrawTest is Bootstrap { + + function test_withdraw_base() public base { + uint32 amount = 1_000_000_000; + _spin(ACTOR, usdx, amount, address(zap)); + vm.startPrank(ACTOR); + uint128 accountId = perpsMarket.createAccount(); + int128 margin = int128(int32(amount)); + usdx.approve(address(perpsMarket), type(uint256).max); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + perpsMarket.modifyCollateral(accountId, 0, margin); + assertEq(usdx.balanceOf(ACTOR), 0); + zap.withdraw({ + _synthId: 0, + _amount: amount, + _accountId: accountId, + _receiver: ACTOR + }); + vm.stopPrank(); + assertEq(usdx.balanceOf(ACTOR), amount); + } + + function test_withdraw_arbitrum() public arbitrum { + uint32 amount = 1_000_000_000; + _spin(ACTOR, usdx, amount, address(zap)); + vm.startPrank(ACTOR); + uint128 accountId = perpsMarket.createAccount(); + int128 margin = int128(int32(amount)); + usdx.approve(address(perpsMarket), type(uint256).max); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + perpsMarket.modifyCollateral(accountId, 0, margin); + assertEq(usdx.balanceOf(ACTOR), 0); + zap.withdraw({ + _synthId: 0, + _amount: amount, + _accountId: accountId, + _receiver: ACTOR + }); + vm.stopPrank(); + assertEq(usdx.balanceOf(ACTOR), amount); + } + +} diff --git a/test/Wrap.t.sol b/test/Wrap.t.sol new file mode 100644 index 0000000..dd829b9 --- /dev/null +++ b/test/Wrap.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract WrapTest is Bootstrap { + + function test_wrap_base(uint32 amount) public base { + _spin(ACTOR, usdc, amount, address(zap)); + assertEq(usdc.balanceOf(ACTOR), amount); + assertEq(susdc.balanceOf(ACTOR), 0); + vm.startPrank(ACTOR); + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(wrapped, DEFAULT_TOLERANCE); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(susdc.balanceOf(ACTOR), wrapped); + } + + function test_wrap_arbitrum(uint32 amount) public arbitrum { + _spin(ACTOR, usdc, amount, address(zap)); + assertEq(usdc.balanceOf(ACTOR), amount); + assertEq(susdc.balanceOf(ACTOR), 0); + vm.startPrank(ACTOR); + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(wrapped, DEFAULT_TOLERANCE); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(susdc.balanceOf(ACTOR), wrapped); + } + +} diff --git a/test/Zap.in.t.sol b/test/Zap.in.t.sol new file mode 100644 index 0000000..9d18652 --- /dev/null +++ b/test/Zap.in.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Zap +} from "./utils/Bootstrap.sol"; + +contract ZapInTest is Bootstrap { + + function test_zap_in_base(uint32 amount) public base { + _spin(ACTOR, usdc, amount, address(zap)); + assertEq(usdc.balanceOf(ACTOR), amount); + assertEq(usdx.balanceOf(ACTOR), 0); + vm.startPrank(ACTOR); + uint256 zapped = zap.zapIn({ + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(zapped, DEFAULT_TOLERANCE); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(usdx.balanceOf(ACTOR), zapped); + } + + function test_zap_in_arbitrum(uint32 amount) public arbitrum { + _spin(ACTOR, usdc, amount, address(zap)); + assertEq(usdc.balanceOf(ACTOR), amount); + assertEq(usdx.balanceOf(ACTOR), 0); + vm.startPrank(ACTOR); + uint256 zapped = zap.zapIn({ + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(zapped, DEFAULT_TOLERANCE); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(usdx.balanceOf(ACTOR), zapped); + } + +} diff --git a/test/Zap.out.t.sol b/test/Zap.out.t.sol new file mode 100644 index 0000000..cc907c8 --- /dev/null +++ b/test/Zap.out.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract ZapOutTest is Bootstrap { + + function test_zap_out_base(uint32 amount) public base { + _spin(ACTOR, usdx, amount, address(zap)); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(usdx.balanceOf(ACTOR), amount); + vm.startPrank(ACTOR); + uint256 zapped = zap.zapOut({ + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(zapped, DEFAULT_TOLERANCE); + assertEq(usdc.balanceOf(ACTOR), zapped); + assertEq(usdx.balanceOf(ACTOR), 0); + } + + function test_zap_out_arbitum(uint32 amount) public arbitrum { + _spin(ACTOR, usdx, amount, address(zap)); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(usdx.balanceOf(ACTOR), amount); + vm.startPrank(ACTOR); + uint256 zapped = zap.zapOut({ + _amount: amount, + _tolerance: DEFAULT_TOLERANCE, + _receiver: ACTOR + }); + vm.stopPrank(); + assertGe(zapped, DEFAULT_TOLERANCE); + assertEq(usdc.balanceOf(ACTOR), zapped); + assertEq(usdx.balanceOf(ACTOR), 0); + } + +} diff --git a/test/Zap.t.sol b/test/Zap.t.sol deleted file mode 100644 index f1decbb..0000000 --- a/test/Zap.t.sol +++ /dev/null @@ -1,352 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import {Arbitrum, Base} from "../script/utils/Parameters.sol"; -import { - ICore, - IERC20, - IPerpsMarket, - IPool, - ISpotMarket, - Zap -} from "../src/Zap.sol"; -import {Test} from "forge-std/Test.sol"; - -contract ZapTest is Test, Arbitrum, Base { - - uint256 BASE; - uint256 ARBITRUM; - Zap zap; - ISpotMarket spotMarket; - IPerpsMarket perpsMarket; - IERC20 usdc; - IERC20 susdc; - IERC20 usdx; - IERC20 weth; - address actor = 0x7777777777777777777777777777777777777777; - uint256 tolerance = 0; - - function setUp() public { - string memory BASE_RPC = vm.envString("BASE_RPC"); - string memory ARB_RPC = vm.envString("ARBITRUM_RPC"); - BASE = vm.createFork(BASE_RPC, 20_165_000); - ARBITRUM = vm.createFork(ARB_RPC, 256_615_000); - } - - modifier base() { - vm.selectFork(BASE); - zap = new Zap({ - _usdc: BASE_USDC, - _usdx: BASE_USDX, - _spotMarket: BASE_SPOT_MARKET, - _perpsMarket: BASE_PERPS_MARKET, - _core: BASE_CORE, - _referrer: BASE_REFERRER, - _susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, - _aave: BASE_AAVE_POOL, - _uniswap: BASE_UNISWAP - }); - spotMarket = ISpotMarket(BASE_SPOT_MARKET); - perpsMarket = IPerpsMarket(BASE_PERPS_MARKET); - usdc = IERC20(BASE_USDC); - susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); - usdx = IERC20(BASE_USDX); - weth = IERC20(0x4200000000000000000000000000000000000006); - _; - } - - modifier arbitrum() { - vm.selectFork(ARBITRUM); - zap = new Zap({ - _usdc: ARBITRUM_USDC, - _usdx: ARBITRUM_USDX, - _spotMarket: ARBITRUM_SPOT_MARKET, - _perpsMarket: ARBITRUM_PERPS_MARKET, - _core: ARBITRUM_CORE, - _referrer: ARBITRUM_REFERRER, - _susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, - _aave: ARBITRUM_AAVE_POOL, - _uniswap: ARBITRUM_UNISWAP - }); - spotMarket = ISpotMarket(ARBITRUM_SPOT_MARKET); - perpsMarket = IPerpsMarket(ARBITRUM_PERPS_MARKET); - usdc = IERC20(ARBITRUM_USDC); - susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); - usdx = IERC20(ARBITRUM_USDX); - weth = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1); - _; - } - - function _spin( - address addr, - IERC20 token, - uint256 amount, - address approved - ) - internal - { - vm.assume(amount > 1e6); - deal(address(token), addr, amount); - vm.prank(addr); - IERC20(token).approve(approved, type(uint256).max); - } - - function test_zap_in_arbitrum(uint32 amount) public arbitrum { - _spin(actor, usdc, amount, address(zap)); - assertEq(usdc.balanceOf(actor), amount); - assertEq(usdx.balanceOf(actor), 0); - vm.startPrank(actor); - uint256 zapped = zap.zapIn({ - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(zapped, tolerance); - assertEq(usdc.balanceOf(actor), 0); - assertEq(usdx.balanceOf(actor), zapped); - } - - function test_zap_in_base(uint32 amount) public base { - _spin(actor, usdc, amount, address(zap)); - assertEq(usdc.balanceOf(actor), amount); - assertEq(usdx.balanceOf(actor), 0); - vm.startPrank(actor); - uint256 zapped = zap.zapIn({ - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(zapped, tolerance); - assertEq(usdc.balanceOf(actor), 0); - assertEq(usdx.balanceOf(actor), zapped); - } - - function test_zap_out_arbitum(uint32 amount) public arbitrum { - _spin(actor, usdx, amount, address(zap)); - assertEq(usdc.balanceOf(actor), 0); - assertEq(usdx.balanceOf(actor), amount); - vm.startPrank(actor); - uint256 zapped = zap.zapOut({ - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(zapped, tolerance); - assertEq(usdc.balanceOf(actor), zapped); - assertEq(usdx.balanceOf(actor), 0); - } - - function test_zap_out_base(uint32 amount) public base { - _spin(actor, usdx, amount, address(zap)); - assertEq(usdc.balanceOf(actor), 0); - assertEq(usdx.balanceOf(actor), amount); - vm.startPrank(actor); - uint256 zapped = zap.zapOut({ - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(zapped, tolerance); - assertEq(usdc.balanceOf(actor), zapped); - assertEq(usdx.balanceOf(actor), 0); - } - - function test_wrap_arbitrum(uint32 amount) public arbitrum { - _spin(actor, usdc, amount, address(zap)); - assertEq(usdc.balanceOf(actor), amount); - assertEq(susdc.balanceOf(actor), 0); - vm.startPrank(actor); - uint256 wrapped = zap.wrap({ - _token: address(usdc), - _synthId: zap.SUSDC_SPOT_ID(), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(wrapped, tolerance); - assertEq(usdc.balanceOf(actor), 0); - assertEq(susdc.balanceOf(actor), wrapped); - } - - function test_wrap_base(uint32 amount) public base { - _spin(actor, usdc, amount, address(zap)); - assertEq(usdc.balanceOf(actor), amount); - assertEq(susdc.balanceOf(actor), 0); - vm.startPrank(actor); - uint256 wrapped = zap.wrap({ - _token: address(usdc), - _synthId: zap.SUSDC_SPOT_ID(), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(wrapped, tolerance); - assertEq(usdc.balanceOf(actor), 0); - assertEq(susdc.balanceOf(actor), wrapped); - } - - function test_unwrap_arbitrum(uint32 amount) public arbitrum { - _spin(actor, usdc, amount, address(zap)); - vm.startPrank(actor); - uint256 wrapped = zap.wrap({ - _token: address(usdc), - _synthId: zap.SUSDC_SPOT_ID(), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - assertEq(usdc.balanceOf(actor), 0); - assertGe(susdc.balanceOf(actor), tolerance); - susdc.approve(address(zap), type(uint256).max); - uint256 unwrapped = zap.unwrap({ - _token: address(usdc), - _synthId: zap.SUSDC_SPOT_ID(), - _amount: wrapped, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(unwrapped, tolerance); - assertEq(usdc.balanceOf(actor), amount); - assertEq(susdc.balanceOf(actor), 0); - } - - function test_unwrap_base(uint32 amount) public base { - _spin(actor, usdc, amount, address(zap)); - vm.startPrank(actor); - uint256 wrapped = zap.wrap({ - _token: address(usdc), - _synthId: zap.SUSDC_SPOT_ID(), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - assertEq(usdc.balanceOf(actor), 0); - assertGe(susdc.balanceOf(actor), tolerance); - susdc.approve(address(zap), type(uint256).max); - uint256 unwrapped = zap.unwrap({ - _token: address(usdc), - _synthId: zap.SUSDC_SPOT_ID(), - _amount: wrapped, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(unwrapped, tolerance); - assertEq(usdc.balanceOf(actor), amount); - assertEq(susdc.balanceOf(actor), 0); - } - - function test_buy_arbitrum(uint32 amount) public arbitrum { - _spin(actor, usdx, amount, address(zap)); - assertEq(usdx.balanceOf(actor), amount); - assertEq(susdc.balanceOf(actor), 0); - vm.startPrank(actor); - (uint256 received, address synth) = zap.buy({ - _synthId: zap.SUSDC_SPOT_ID(), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertEq(synth, address(susdc)); - assertGe(received, tolerance); - assertEq(usdx.balanceOf(actor), 0); - assertGe(susdc.balanceOf(actor), tolerance); - } - - function test_sell_arbitrum(uint32 amount) public arbitrum { - _spin(actor, usdx, amount, address(zap)); - vm.startPrank(actor); - (uint256 received,) = zap.buy({ - _synthId: zap.SUSDC_SPOT_ID(), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - assertEq(usdx.balanceOf(actor), 0); - assertGe(susdc.balanceOf(actor), tolerance); - susdc.approve(address(zap), type(uint256).max); - received = zap.sell({ - _synthId: zap.SUSDC_SPOT_ID(), - _amount: received, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(received, tolerance); - assertGe(usdx.balanceOf(actor), tolerance); - assertEq(susdc.balanceOf(actor), 0); - } - - function test_sell_base(uint32 amount) public base { - _spin(actor, usdx, amount, address(zap)); - vm.startPrank(actor); - (uint256 received,) = zap.buy({ - _synthId: zap.SUSDC_SPOT_ID(), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - assertEq(usdx.balanceOf(actor), 0); - assertGe(susdc.balanceOf(actor), tolerance); - susdc.approve(address(zap), type(uint256).max); - received = zap.sell({ - _synthId: zap.SUSDC_SPOT_ID(), - _amount: received, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertGe(received, tolerance); - assertGe(usdx.balanceOf(actor), tolerance); - assertEq(susdc.balanceOf(actor), 0); - } - - function test_swap_arbitrum() public arbitrum { - uint256 amount = 1 ether; - _spin(actor, weth, amount, address(zap)); - assertEq(weth.balanceOf(actor), amount); - assertEq(usdc.balanceOf(actor), 0); - vm.startPrank(actor); - uint256 received = zap.swap({ - _from: address(weth), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertEq(weth.balanceOf(actor), 0); - assertEq(usdc.balanceOf(actor), received); - assertTrue(received > 0); - } - - function test_swap_base() public arbitrum { - uint256 amount = 1 ether; - _spin(actor, weth, amount, address(zap)); - assertEq(weth.balanceOf(actor), amount); - assertEq(usdc.balanceOf(actor), 0); - vm.startPrank(actor); - uint256 received = zap.swap({ - _from: address(weth), - _amount: amount, - _tolerance: tolerance, - _receiver: actor - }); - vm.stopPrank(); - assertEq(weth.balanceOf(actor), 0); - assertEq(usdc.balanceOf(actor), received); - assertTrue(received > 0); - } - - function test_flashloan() public arbitrum { - /// @custom:todo - } - -} diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol new file mode 100644 index 0000000..b9f87c9 --- /dev/null +++ b/test/utils/Bootstrap.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Arbitrum, Base} from "../../script/utils/Parameters.sol"; +import { + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Zap +} from "../../src/Zap.sol"; +import {Constants} from "../utils/Constants.sol"; +import {Test} from "forge-std/Test.sol"; + +contract Bootstrap is Test, Base, Arbitrum, Constants { + + uint256 BASE; + uint256 ARBITRUM; + + Zap zap; + + ICore core; + ISpotMarket spotMarket; + IPerpsMarket perpsMarket; + + IERC20 usdc; + IERC20 susdc; + IERC20 usdx; + IERC20 weth; + + function setUp() public { + string memory BASE_RPC = vm.envString(BASE_RPC_REF); + string memory ARBITRUM_RPC = vm.envString(ARBITRUM_RPC_REF); + + BASE = vm.createFork(BASE_RPC); + ARBITRUM = vm.createFork(ARBITRUM_RPC); + } + + modifier base() { + vm.selectFork(BASE); + zap = new Zap({ + _usdc: BASE_USDC, + _usdx: BASE_USDX, + _spotMarket: BASE_SPOT_MARKET, + _perpsMarket: BASE_PERPS_MARKET, + _core: BASE_CORE, + _referrer: BASE_REFERRER, + _susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, + _aave: BASE_AAVE_POOL, + _uniswap: BASE_UNISWAP + }); + core = ICore(BASE_CORE); + spotMarket = ISpotMarket(BASE_SPOT_MARKET); + perpsMarket = IPerpsMarket(BASE_PERPS_MARKET); + usdc = IERC20(BASE_USDC); + susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); + usdx = IERC20(BASE_USDX); + weth = IERC20(BASE_WETH); + _; + } + + modifier arbitrum() { + vm.selectFork(ARBITRUM); + zap = new Zap({ + _usdc: ARBITRUM_USDC, + _usdx: ARBITRUM_USDX, + _spotMarket: ARBITRUM_SPOT_MARKET, + _perpsMarket: ARBITRUM_PERPS_MARKET, + _core: ARBITRUM_CORE, + _referrer: ARBITRUM_REFERRER, + _susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, + _aave: ARBITRUM_AAVE_POOL, + _uniswap: ARBITRUM_UNISWAP + }); + core = ICore(ARBITRUM_CORE); + spotMarket = ISpotMarket(ARBITRUM_SPOT_MARKET); + perpsMarket = IPerpsMarket(ARBITRUM_PERPS_MARKET); + usdc = IERC20(ARBITRUM_USDC); + susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); + usdx = IERC20(ARBITRUM_USDX); + weth = IERC20(ARBITRUM_WETH); + _; + } + + function _spin( + address eoa, + IERC20 token, + uint256 amount, + address approved + ) + internal + { + vm.assume(amount > 1e6); + deal(address(token), eoa, amount); + vm.prank(eoa); + IERC20(token).approve(approved, type(uint256).max); + } + +} diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol new file mode 100644 index 0000000..8e7184c --- /dev/null +++ b/test/utils/Constants.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +contract Constants { + + /// @custom:zap + string constant BASE_RPC_REF = "BASE_RPC"; + string constant ARBITRUM_RPC_REF = "ARBITRUM_RPC"; + uint256 constant BASE_FORK_BLOCK = 20_165_000; + uint256 constant ARBITRUM_FORK_BLOCK_DEBT = 256_215_787; + uint256 constant ARBITRUM_FORK_BLOCK = 256_615_000; + address constant ACTOR = 0x7777777777777777777777777777777777777777; + address constant ARBITRUM_BOB = 0x3bd5e344Ac629C9f232F921bAfDeEEc312deAC9b; + uint128 constant ARBITRUM_BOB_ID = + 170_141_183_460_469_231_731_687_303_715_884_105_912; + uint256 constant DEFAULT_TOLERANCE = 0; + + /// @custom:tokens + address constant ARBITRUM_WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + address constant BASE_WETH = 0x4200000000000000000000000000000000000006; + + /// @custom:synthetix + bytes32 constant _ADMIN_PERMISSION = "ADMIN"; + bytes32 constant _WITHDRAW_PERMISSION = "WITHDRAW"; + bytes32 constant _DELEGATE_PERMISSION = "DELEGATE"; + bytes32 constant _MINT_PERMISSION = "MINT"; + bytes32 constant _REWARDS_PERMISSION = "REWARDS"; + bytes32 constant _PERPS_MODIFY_COLLATERAL_PERMISSION = + "PERPS_MODIFY_COLLATERAL"; + bytes32 constant _PERPS_COMMIT_ASYNC_ORDER_PERMISSION = + "PERPS_COMMIT_ASYNC_ORDER"; + bytes32 constant _BURN_PERMISSION = "BURN"; + bytes32 constant _BFP_PERPS_PAY_DEBT_PERMISSION = "BFP_PERPS_PAY_DEBT"; + bytes32 constant _BFP_PERPS_SPLIT_ACCOUNT_PERMISSION = + "BFP_PERPS_SPLIT_ACCOUNT"; + +} diff --git a/test/utils/errors/SynthetixV3Errors.sol b/test/utils/errors/SynthetixV3Errors.sol index 88bdc80..1660ba6 100644 --- a/test/utils/errors/SynthetixV3Errors.sol +++ b/test/utils/errors/SynthetixV3Errors.sol @@ -5,8 +5,120 @@ pragma solidity 0.8.27; /// @author JaredBorders (jaredborders@pm.me) contract SynthetixV3Errors { + enum SettlementStrategyType { + PYTH + } + error WrapperExceedsMaxAmount( uint256 maxWrappableAmount, uint256 currentSupply, uint256 amountToWrap ); + error OffchainLookup( + address sender, + string[] urls, + bytes callData, + bytes4 callbackFunction, + bytes extraData + ); + error ArbGasPriceOracleInvalidExecutionKind(); + error InvalidId(uint128 id); + error FeatureUnavailable(bytes32 which); + error InvalidVerificationResponse(); + error InvalidSettlementStrategy(uint256 settlementStrategyId); + error MinimumSettlementAmountNotMet(uint256 minimum, uint256 actual); + error SettlementStrategyNotFound(SettlementStrategyType strategyType); + error InvalidFeeCollectorInterface(address invalidFeeCollector); + error InvalidReferrerShareRatio(uint256 shareRatioD18); + error NotEligibleForLiquidation(uint128 accountId); + error InvalidAmountDelta(int256 amountDelta); + error InvalidArgument(); + error InvalidUpdateDataSource(); + error InvalidUpdateData(); + error InsufficientFee(); + error NoFreshUpdate(); + error PriceFeedNotFoundWithinRange(); + error PriceFeedNotFound(); + error StalePrice(); + error InvalidWormholeVaa(); + error InvalidGovernanceMessage(); + error InvalidGovernanceTarget(); + error InvalidGovernanceDataSource(); + error OldGovernanceMessage(); + error SettlementWindowNotOpen(uint256 timestamp, uint256 settlementTime); + error SettlementWindowExpired( + uint256 timestamp, uint256 settlementTime, uint256 settlementExpiration + ); + error SettlementWindowNotExpired( + uint256 timestamp, uint256 settlementTime, uint256 settlementExpiration + ); + error OrderNotValid(); + error AcceptablePriceExceeded(uint256 fillPrice, uint256 acceptablePrice); + error PendingOrderExists(); + error ZeroSizeOrder(); + error InsufficientMargin(int256 availableMargin, uint256 minMargin); + error MaxCollateralExceeded( + uint128 synthMarketId, + uint256 maxAmount, + uint256 collateralAmount, + uint256 depositAmount + ); + error SynthNotEnabledForCollateral(uint128 synthMarketId); + error InsufficientCollateral( + uint128 synthMarketId, uint256 collateralAmount, uint256 withdrawAmount + ); + error InsufficientCollateralAvailableForWithdraw( + uint256 available, uint256 required + ); + error InsufficientMarginError(uint256 leftover); + error AccountLiquidatable(uint128 accountId); + error MaxPositionsPerAccountReached(uint128 maxPositionsPerAccount); + error MaxCollateralsPerAccountReached(uint128 maxCollateralsPerAccount); + error InvalidMarket(uint128 marketId); + error PriceFeedNotSet(uint128 marketId); + error MarketAlreadyExists(uint128 marketId); + error MaxOpenInterestReached( + uint128 marketId, uint256 maxMarketSize, int256 newSideSize + ); + error PerpsMarketNotInitialized(); + error PerpsMarketAlreadyInitialized(); + error PriceDeviationToleranceExceeded(uint256 deviation, uint256 tolerance); + error ExceedsMaxUsdAmount(uint256 maxUsdAmount, uint256 usdAmountCharged); + error ExceedsMaxSynthAmount( + uint256 maxSynthAmount, uint256 synthAmountCharged + ); + error InsufficientAmountReceived(uint256 expected, uint256 current); + error InvalidPrices(); + error InvalidWrapperFees(); + error NotNominated(address addr); + error InvalidMarketOwner(); + error InsufficientSharesAmount(uint256 expected, uint256 actual); + error OutsideSettlementWindow( + uint256 timestamp, uint256 startTime, uint256 expirationTime + ); + error IneligibleForCancellation(uint256 timestamp, uint256 expirationTime); + error OrderAlreadySettled(uint256 asyncOrderId, uint256 settledAt); + error InvalidClaim(uint256 asyncOrderId); + error OnlyAccountTokenProxy(address origin); + error PermissionNotGranted( + uint128 accountId, bytes32 permission, address user + ); + error InsufficientSynthCollateral( + uint128 collateralId, uint256 collateralAmount, uint256 withdrawAmount + ); + error InsufficientAccountMargin(uint256 leftover); + error AccountMarginLiquidatable(uint128 accountId); + error NonexistentDebt(uint128 accountId); + error InvalidAccountId(uint128 accountId); + error Unauthorized(address addr); + error CannotSelfApprove(address addr); + error InvalidTransferRecipient(address addr); + error InvalidOwner(address addr); + error TokenDoesNotExist(uint256 id); + error TokenAlreadyMinted(uint256 id); + error PermissionDenied( + uint128 accountId, bytes32 permission, address target + ); + error InsufficientBalance(uint256 required, uint256 existing); + error InsufficientAllowance(uint256 required, uint256 existing); + error InvalidParameter(string parameter, string reason); } From bd21d6437a4e9582cd2c2aad7abec93a25cf3c9a Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 01:46:31 -0400 Subject: [PATCH 025/129] =?UTF-8?q?=F0=9F=9A=80=20add=20arbitrum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployments/{BaseGoerli.json => Arbitrum.json} | 0 deployments/Optimism.json | 0 deployments/OptimismGoerli.json | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename deployments/{BaseGoerli.json => Arbitrum.json} (100%) delete mode 100644 deployments/Optimism.json delete mode 100644 deployments/OptimismGoerli.json diff --git a/deployments/BaseGoerli.json b/deployments/Arbitrum.json similarity index 100% rename from deployments/BaseGoerli.json rename to deployments/Arbitrum.json diff --git a/deployments/Optimism.json b/deployments/Optimism.json deleted file mode 100644 index e69de29..0000000 diff --git a/deployments/OptimismGoerli.json b/deployments/OptimismGoerli.json deleted file mode 100644 index e69de29..0000000 From 41ab9d65a30ed31ac4faf927ea23d198eddbe6ac Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 01:46:47 -0400 Subject: [PATCH 026/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env-example b/.env-example index c8a9394..5da1691 100644 --- a/.env-example +++ b/.env-example @@ -1,5 +1,5 @@ BASE_RPC=https://base-mainnet.g.alchemy.com/v2/KEY -ARBITRUM_RPC=https://arbitrum-mainnet.infura.io/v3/KEY +ARBITRUM_RPC=https://arb-mainnet.g.alchemy.com/v2/KEY PRIVATE_KEY=KEY BASESCAN_API_KEY=KEY ARBISCAN_API_KEY=KEY \ No newline at end of file From 4419661058b2b1fa37393f5cc83a93b659bf3ed0 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 01:47:00 -0400 Subject: [PATCH 027/129] =?UTF-8?q?=F0=9F=91=B7=20adjust=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Errors.sol | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Errors.sol b/src/Errors.sol index c7d6d8a..07986a5 100644 --- a/src/Errors.sol +++ b/src/Errors.sol @@ -13,12 +13,19 @@ contract Errors { /// @param reason string for the failure error UnwrapFailed(string reason); + /// @notice thrown when a buy operation fails + /// @param reason string for the failure + error BuyFailed(string reason); + + /// @notice thrown when a sell operation fails + /// @param reason string for the failure + error SellFailed(string reason); + /// @notice thrown when a swap operation fails /// @param reason string for the failure error SwapFailed(string reason); - /// @notice thrown when an address who is not permitted - /// to modify an account's collateral attempts to - error NotPermittedToModifyCollateral(); + /// @notice thrown when operation is not permitted + error NotPermitted(); } From b8f5772913883ac194bfb22a804c8c09ee5b6ca9 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 01:47:13 -0400 Subject: [PATCH 028/129] =?UTF-8?q?=F0=9F=9A=80=20update=20deploy=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 1d6bf96..cfd330e 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -8,8 +8,8 @@ import {Zap} from "../src/Zap.sol"; /// @author JaredBorders (jaredborders@pm.me) contract Setup is Script { + /// @custom:todo function deploySystem() public returns (address zap) { - /// @custom:todo // zap = address(new Zap()); } From a201b4ee46a5074d336ec4780e7522f5e5d2f5f9 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 01:47:36 -0400 Subject: [PATCH 029/129] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20remove=20fluff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Events.sol | 62 -------------------------------------------------- src/Types.sol | 56 --------------------------------------------- 2 files changed, 118 deletions(-) delete mode 100644 src/Events.sol delete mode 100644 src/Types.sol diff --git a/src/Events.sol b/src/Events.sol deleted file mode 100644 index 6c365b0..0000000 --- a/src/Events.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -/// @title zap events -/// @author @jaredborders -contract Events { -// /// @notice emitted when a zap-in operation is performed -// /// @param initiator address that initiated the zap-in -// /// @param marketId identifier of the spot market -// /// @param collateralAmount exchanged for USD -// /// @param usdAmount amount of USD received -// /// @param receiver address that received the USD -// event ZapIn( -// address indexed initiator, -// uint128 indexed marketId, -// uint256 collateralAmount, -// uint256 usdAmount, -// address indexed receiver -// ); - -// /// @notice emitted when a zap-out operation is performed -// /// @param initiator address that initiated the zap-out -// /// @param marketId identifier of the spot market -// /// @param usdAmount exchanged for collateral -// /// @param collateralAmount amount of collateral received -// /// @param receiver address that received the collateral -// event ZapOut( -// address indexed initiator, -// uint128 indexed marketId, -// uint256 usdAmount, -// uint256 collateralAmount, -// address indexed receiver -// ); - -// /// @notice emitted when a wrap operation is performed -// /// @param initiator Address that initiated the wrap -// /// @param marketId ID of the market involved -// /// @param collateralAmount Amount of collateral wrapped -// /// @param synthAmount Amount of synth received -// /// @param receiver Address that received the synth tokens -// event Wrapped( -// address indexed initiator, -// uint128 indexed marketId, -// uint256 collateralAmount, -// uint256 synthAmount, -// address indexed receiver -// ); - -// /// @notice emitted when an unwrap operation is performed -// /// @param initiator Address that initiated the unwrap -// /// @param marketId ID of the market involved -// /// @param synthAmount Amount of synth unwrapped -// /// @param collateralAmount Amount of collateral received -// /// @param receiver Address that received the collateral tokens -// event Unwrapped( -// address indexed initiator, -// uint128 indexed marketId, -// uint256 synthAmount, -// uint256 collateralAmount, -// address indexed receiver -// ); -} diff --git a/src/Types.sol b/src/Types.sol deleted file mode 100644 index e4f758b..0000000 --- a/src/Types.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -contract Types { - - /// @notice enumerated type for the direction of a zap operation - /// @custom:IN collateral -> synthetix stablecoin - /// @custom:OUT synthetix collateral -> collateral - enum Direction { - IN, - OUT - } - - /// @notice tolerance levels for wrap and swap operations - /// @param tolerableWrapAmount minimum acceptable amount for wrap operation - /// @param tolerableSwapAmount minimum acceptable amount for swap operation - struct Tolerance { - uint256 tolerableWrapAmount; - uint256 tolerableSwapAmount; - } - - /// @notice structure of data required for any zap operation - /// @param spotMarket address of the spot market proxy - /// @param collateral address of the collateral token - /// @param marketId identifier of the spot market - /// @param amount of tokens to zap - /// @param tolerance levels for wrap and swap operations - /// @param direction of the zap operation - /// @param receiver address to receive the output tokens - /// @param referrer address of the referrer - struct ZapData { - address spotMarket; - address collateral; - uint128 marketId; - uint256 amount; - Tolerance tolerance; - Direction direction; - address receiver; - address referrer; - } - - /// @notice structure of data required for a flash loan operation - /// @param asset address of the asset to be borrowed - /// @param amount of the asset to be borrowed - /// @param premium fee to be paid for the flash loan - /// @param initiator address of the flash loan operation - /// @param params parameters needed for initiating the loan - struct FlashData { - address asset; - uint256 amount; - uint256 premium; - address initiator; - bytes params; - } - -} From 8f6a344e1e773b8cd84abfcca57489b4307ecf48 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 01:47:50 -0400 Subject: [PATCH 030/129] =?UTF-8?q?=F0=9F=91=B7=20add=20interfaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/IAave.sol | 1 + src/interfaces/ISynthetix.sol | 48 +++++++++++++++++++++++++++++++++++ src/interfaces/IUniswap.sol | 1 + 3 files changed, 50 insertions(+) diff --git a/src/interfaces/IAave.sol b/src/interfaces/IAave.sol index a59da51..8bd5ec3 100644 --- a/src/interfaces/IAave.sol +++ b/src/interfaces/IAave.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +/// @custom:todo remove extraneous code library DataTypes { struct ReserveData { diff --git a/src/interfaces/ISynthetix.sol b/src/interfaces/ISynthetix.sol index eb563be..7d5af63 100644 --- a/src/interfaces/ISynthetix.sol +++ b/src/interfaces/ISynthetix.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +/// @custom:todo remove extraneous code + /// @title Consolidated Spot Market Proxy Interface /// @notice Responsible for interacting with Synthetix v3 spot markets /// @author Synthetix @@ -130,6 +132,34 @@ interface ISpotMarket { } +interface IERC7412 { + + /// @dev Emitted when an oracle is requested to provide data. + /// Upon receipt of this error, a wallet client + /// should automatically resolve the requested oracle data + /// and call fulfillOracleQuery. + /// @param oracleContract The address of the oracle contract + /// (which is also the fulfillment contract). + /// @param oracleQuery The query to be sent to the off-chain interface. + error OracleDataRequired(address oracleContract, bytes oracleQuery); + + /// @dev Emitted when the recently posted oracle data requires + /// a fee to be paid. Upon receipt of this error, + /// a wallet client should attach the requested feeAmount + /// to the most recently posted oracle data transaction + error FeeRequired(uint256 feeAmount); + + /// @dev Upon resolving the oracle query, the client should + /// call this function to post the data to the + /// blockchain. + /// @param signedOffchainData The data that was returned + /// from the off-chain interface, signed by the oracle. + function fulfillOracleQuery(bytes calldata signedOffchainData) + external + payable; + +} + interface IPerpsMarket { /// @notice modify the collateral delegated to the account @@ -158,6 +188,24 @@ interface IPerpsMarket { ) external; + function createAccount() external returns (uint128 accountId); + + function grantPermission( + uint128 accountId, + bytes32 permission, + address user + ) + external; + + function isAuthorized( + uint128 accountId, + bytes32 permission, + address target + ) + external + view + returns (bool isAuthorized); + } interface ICore { diff --git a/src/interfaces/IUniswap.sol b/src/interfaces/IUniswap.sol index 8dd44ae..f30d281 100644 --- a/src/interfaces/IUniswap.sol +++ b/src/interfaces/IUniswap.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +/// @custom:todo add natspec interface IUniswap { struct ExactInputSingleParams { From fca0de633e114a4e41ffc9bdc91863733a149a07 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 01:48:03 -0400 Subject: [PATCH 031/129] =?UTF-8?q?=F0=9F=91=B7=20update=20zap=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 152 ++++++++++++++++++++++++++++------------------------ 1 file changed, 82 insertions(+), 70 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 95f62f0..885e373 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -2,14 +2,21 @@ pragma solidity 0.8.27; import {Errors} from "./Errors.sol"; -import {Events} from "./Events.sol"; -import {Types} from "./Types.sol"; import {IPool} from "./interfaces/IAave.sol"; import {IERC20} from "./interfaces/IERC20.sol"; import {ICore, IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; import {IUniswap} from "./interfaces/IUniswap.sol"; -contract Zap is Types, Errors, Events { +/// @title Zap +/// @custom:synthetix Zap USDC into and out of USDx +/// @custom:aave Flashloan USDC to unwind synthetix collateral +/// @custom:uniswap Swap unwound collateral for USDC to repay flashloan +/// @dev Idle token balances are not safe +/// @dev Intended for standalone use; do not inherit +/// @author @jaredborders +/// @author @barrasso +/// @author @Flocqst +contract Zap is Errors { /// @custom:synthetix address public immutable USDC; @@ -21,6 +28,8 @@ contract Zap is Types, Errors, Events { uint128 public immutable SUSDC_SPOT_ID; uint128 public immutable PREFFERED_POOL_ID; bytes32 public immutable MODIFY_PERMISSION; + bytes32 public immutable BURN_PERMISSION; + uint128 public immutable USDX_ID; /// @custom:aave address public immutable AAVE; @@ -28,7 +37,6 @@ contract Zap is Types, Errors, Events { /// @custom:uniswap address public immutable UNISWAP; - address public immutable FACTORY; uint24 public immutable FEE_TIER; constructor( @@ -42,7 +50,7 @@ contract Zap is Types, Errors, Events { address _aave, address _uniswap ) { - /// @custom:synthetix address assignment + /// @custom:synthetix USDC = _usdc; USDX = _usdx; SPOT_MARKET = _spotMarket; @@ -52,6 +60,8 @@ contract Zap is Types, Errors, Events { SUSDC_SPOT_ID = _susdcSpotId; PREFFERED_POOL_ID = ICore(CORE).getPreferredPool(); MODIFY_PERMISSION = "PERPS_MODIFY_COLLATERAL"; + BURN_PERMISSION = "BURN"; + USDX_ID = 0; /// @custom:aave AAVE = _aave; @@ -59,10 +69,13 @@ contract Zap is Types, Errors, Events { /// @custom:uniswap UNISWAP = _uniswap; - FACTORY = 0x33128a8fC17869897dcE68Ed026d694621f6FDfD; FEE_TIER = 3000; } + /*////////////////////////////////////////////////////////////// + ZAP + //////////////////////////////////////////////////////////////*/ + function zapIn( uint256 _amount, uint256 _tolerance, @@ -111,6 +124,10 @@ contract Zap is Types, Errors, Events { zapped = _unwrap(SUSDC_SPOT_ID, zapped, _tolerance); } + /*////////////////////////////////////////////////////////////// + WRAP AND UNWRAP + //////////////////////////////////////////////////////////////*/ + function wrap( address _token, uint128 _synthId, @@ -186,6 +203,10 @@ contract Zap is Types, Errors, Events { } } + /*////////////////////////////////////////////////////////////// + BUY AND SELL + //////////////////////////////////////////////////////////////*/ + function buy( uint128 _synthId, uint256 _amount, @@ -218,7 +239,7 @@ contract Zap is Types, Errors, Events { received = amount; synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); } catch Error(string memory reason) { - revert SwapFailed(reason); + revert BuyFailed(reason); } } @@ -255,37 +276,13 @@ contract Zap is Types, Errors, Events { }) returns (uint256 amount, ISpotMarket.Data memory) { received = amount; } catch Error(string memory reason) { - revert SwapFailed(reason); + revert SellFailed(reason); } } - function _pull( - address _token, - address _from, - uint256 _amount - ) - internal - returns (bool success) - { - IERC20 token = IERC20(_token); - success = token.transferFrom(_from, address(this), _amount); - } - - function _push( - address _token, - address _receiver, - uint256 _amount - ) - internal - returns (bool success) - { - if (_receiver == address(this)) { - success = true; - } else { - IERC20 token = IERC20(_token); - success = token.transfer(_receiver, _amount); - } - } + /*////////////////////////////////////////////////////////////// + AAVE + //////////////////////////////////////////////////////////////*/ function requestFlashloan( uint256 _usdcLoan, @@ -308,7 +305,6 @@ contract Zap is Types, Errors, Events { _swapTolerance, receiver ); - IPool(AAVE).flashLoanSimple({ receiverAddress: address(this), asset: USDC, @@ -340,7 +336,6 @@ contract Zap is Types, Errors, Events { params, (uint256, address, uint128, uint128, uint256, uint256, address) ); - uint256 unwoundCollateral = _unwind( amount, collateralAmount, @@ -352,37 +347,10 @@ contract Zap is Types, Errors, Events { ); uint256 debt = amount + premium; uint256 differece = unwoundCollateral - debt; - IERC20(USDC).approve(AAVE, debt); - return IERC20(collateral).transfer(receiver, differece); } - function unwind( - uint256 _usdcAmount, - uint256 _collateralAmount, - address _collateral, - uint128 _accountId, - uint128 _synthId, - uint256 _tolerance, - uint256 _swapTolerance, - address _receiver - ) - external - { - _pull(USDC, msg.sender, _usdcAmount); - _unwind( - _usdcAmount, - _collateralAmount, - _collateral, - _accountId, - _synthId, - _tolerance, - _swapTolerance - ); - _push(_collateral, _receiver, _collateralAmount); - } - function _unwind( uint256 _usdcLoan, uint256 _collateralAmount, @@ -399,22 +367,31 @@ contract Zap is Types, Errors, Events { _burn(usdxAmount, _accountId); _withdraw(_synthId, _collateralAmount, _accountId); unwound = _unwrap(_synthId, _collateralAmount, _tolerance); - if (_synthId != SUSDC_SPOT_ID) { _swap(_collateral, unwound, _swapTolerance); } } + /*////////////////////////////////////////////////////////////// + BURN + //////////////////////////////////////////////////////////////*/ + function burn(uint256 _amount, uint128 _accountId) external { _pull(USDX, msg.sender, _amount); _burn(_amount, _accountId); } + /// @custom:account permission required: "BURN" function _burn(uint256 _amount, uint128 _accountId) internal { IERC20(USDX).approve(CORE, _amount); ICore(CORE).burnUsd(_accountId, PREFFERED_POOL_ID, USDC, _amount); + ICore(CORE).renouncePermission(_accountId, BURN_PERMISSION); } + /*////////////////////////////////////////////////////////////// + WITHDRAW + //////////////////////////////////////////////////////////////*/ + function withdraw( uint128 _synthId, uint256 _amount, @@ -424,10 +401,13 @@ contract Zap is Types, Errors, Events { external { _withdraw(_synthId, _amount, _accountId); - address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); + address synth = _synthId == USDX_ID + ? USDX + : ISpotMarket(SPOT_MARKET).getSynth(_synthId); _push(synth, _receiver, _amount); } + /// @custom:account permission required: "PERPS_MODIFY_COLLATERAL" function _withdraw( uint128 _synthId, uint256 _amount, @@ -436,9 +416,6 @@ contract Zap is Types, Errors, Events { internal { IPerpsMarket market = IPerpsMarket(PERPS_MARKET); - bool canModify = - market.hasPermission(_accountId, MODIFY_PERMISSION, address(this)); - require(canModify, NotPermittedToModifyCollateral()); market.modifyCollateral({ accountId: _accountId, synthMarketId: _synthId, @@ -447,6 +424,10 @@ contract Zap is Types, Errors, Events { market.renouncePermission(_accountId, MODIFY_PERMISSION); } + /*////////////////////////////////////////////////////////////// + UNISWAP + //////////////////////////////////////////////////////////////*/ + function swap( address _from, uint256 _amount, @@ -470,7 +451,6 @@ contract Zap is Types, Errors, Events { returns (uint256 received) { IERC20(_from).approve(UNISWAP, _amount); - IUniswap.ExactInputSingleParams memory params = IUniswap .ExactInputSingleParams({ tokenIn: _from, @@ -491,4 +471,36 @@ contract Zap is Types, Errors, Events { } } + /*////////////////////////////////////////////////////////////// + TRANSFERS + //////////////////////////////////////////////////////////////*/ + + function _pull( + address _token, + address _from, + uint256 _amount + ) + internal + returns (bool success) + { + IERC20 token = IERC20(_token); + success = token.transferFrom(_from, address(this), _amount); + } + + function _push( + address _token, + address _receiver, + uint256 _amount + ) + internal + returns (bool success) + { + if (_receiver == address(this)) { + success = true; + } else { + IERC20 token = IERC20(_token); + success = token.transfer(_receiver, _amount); + } + } + } From f6e76fad3b072838049eb65b0aaab40150eae9af Mon Sep 17 00:00:00 2001 From: JaredBorders <44350516+JaredBorders@users.noreply.github.com> Date: Tue, 24 Sep 2024 02:19:38 -0400 Subject: [PATCH 032/129] =?UTF-8?q?=F0=9F=93=9A=20Update=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 80 +++++++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index e988f58..6abd473 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Zap +# Zap Flashloan Utility [![Github Actions][gha-badge]][gha] [![Foundry][foundry-badge]][foundry] @@ -11,63 +11,55 @@ [license]: https://opensource.org/license/GPL-3.0/ [license-badge]: https://img.shields.io/badge/GitHub-GPL--3.0-informational -## Aave Flashloans +## Overview -Execution Flow -For developers, a helpful mental model to consider when developing your solution: -Your contract calls the Pool contract, requesting a Flash Loan of a certain amount(s) of reserve(s) using flashLoanSimple() or flashLoan(). -After some sanity checks, the Pool transfers the requested amounts of the reserves to your contract, then calls executeOperation() on receiver contract . -Your contract, now holding the flash loaned amount(s), executes any arbitrary operation in its code. -If you are performing a flashLoanSimple, then when your code has finished, you approve Pool for flash loaned amount + fee. -If you are performing flashLoan, then for all the reserves either depending on interestRateMode passed for the asset, either the Pool must be approved for flash loaned amount + fee or must or sufficient collateral or credit delegation should be available to open debt position. -If the amount owing is not available (due to a lack of balance or approval or insufficient collateral for debt), then the transaction is reverted. -All of the above happens in 1 transaction (hence in a single ethereum block). +Zap is a smart contract that facilitates stablecoin "zapping" and Synthetix native collateral unwinding by integrating flash loans from Aave and Uniswap v3 swaps. -The flash loan fee is initialized at deployment to 0.05% and can be updated via Governance Vote. Use FLASHLOAN_PREMIUM_TOTAL to get current value. -Flashloan fee can be shared by the LPs (liquidity providers) and the protocol treasury. The FLASHLOAN_PREMIUM_TOTAL represents the total fee paid by the borrowers of which: -Fee to LP: FLASHLOAN_PREMIUM_TOTAL - FLASHLOAN_PREMIUM_TO_PROTOCOL -Fee to Protocol: FLASHLOAN_PREMIUM_TO_PROTOCOL +### What is a **Zap**? +> ONLY USDC is supported -The pool.sol contract is the main user facing contract of the protocol. It exposes the liquidity management methods that can be invoked using either Solidity or Web3 libraries. +USDC <--(spot market)--> sUSDC <--(spot market)--> USDx -flashLoanSimple -function flashLoanSimple( address receiverAddress, address asset, uint256 amount, bytes calldata params, uint16 referralCode) -Allows users to access liquidity of one reserve or one transaction as long as the amount taken plus fee is returned. +### What is **Collateral Unwinding**? -## Tests +#### Flashloan Utility Flow -1. Follow the [Foundry guide to working on an existing project](https://book.getfoundry.sh/projects/working-on-an-existing-project.html) +1. **Request Flash Loans (Aave):** Borrow USDC to access liquidity without posting collateral. +2. **Zap into USDx (Synthetix Spot Market):** Use the borrowed funds to zap into USDx. +3. **Burn USDx & Repay Debt (Synthetix Core):** Repay Synthetix debt by burning USDx. +4. **Withdraw and Unwrap Collateral (Synthetix Spot Market):** Withdraw margin (e.g., sETH) and convert it back to underlying assets (e.g., WETH). +5. **Swap (Uniswap):** Exchange collateral assets (like WETH) for USDC to repay the flash loan. +6. **Flash Loan Repayment (Aave):** The USDC loan, including the premium, is repaid to Aave. +7. **Send Remaining Collateral (Synthetix):** Any surplus collateral is returned to the user. -2. Build project +## Key Features -``` -npm run compile -``` +- Zap via Synthetix +- Wrap & Unwrap Collateral via Synthetix +- Buy & Sell via Synthetix +- Unwind Collateral via Synthetix, Aave, and Uniswap +- Burn Debt via Synthetix +- Withdraw Perp Collateral via Synthetix +- Swap via Uniswap -3. Execute tests (requires rpc url(s) to be set in `.env`) +## Build and Test -``` -npm run test -``` +### Build and Run -4. Run specific test +1. **Build the project** + ```bash + forge build + ``` -``` -forge test --fork-url $(grep BASE_RPC_URL .env | cut -d '=' -f2) --match-test TEST_NAME -vvv -``` +2. **Run tests** + ```bash + forge test + ``` -## Deployment Addresses +## Deployment -> See `deployments/` folder - -1. Optimism deployments found in `deployments/Optimism.json` -2. Optimism Goerli deployments found in `deployments/OptimismGoerli.json` -3. Base deployments found in `deployments/Base.json` -4. Base Goerli deployments found in `deployments/BaseGoerli.json` +- See the `deployments/` folder for Arbitrum and Base deployments. ## Audits -> See `audits/` folder - -1. Internal audits found in `audits/internal/` -2. External audits found in `audits/external/` \ No newline at end of file +- See the `audits/` folder for Audit reports. From c200cea255a384281240bf21d5d34425935537c5 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 10:46:09 -0400 Subject: [PATCH 033/129] =?UTF-8?q?=E2=98=82=EF=B8=8F=20update=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/coverage.yml | 65 +++++++++++++++++++--------------- .github/workflows/lint.yml | 42 +++++++++++++--------- .github/workflows/test.yml | 48 +++++++++++++------------ 3 files changed, 88 insertions(+), 67 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1f9fc03..841b124 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,34 +1,43 @@ name: coverage -on: [push] +on: + push: + pull_request: + workflow_dispatch: env: - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: ci jobs: - check: - strategy: - fail-fast: true - - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Report Coverage - run: | - forge coverage --fork-url ${{ secrets.BASE_RPC_URL }} --report lcov - - - name: Upload Coverage Report - uses: codecov/codecov-action@v3 - with: - files: ./lcov.info - name: codecov-unit - fail_ci_if_error: true - verbose: true + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge coverage + run: | + forge coverage --report lcov + id: coverage + + - name: Upload coverage report + uses: codecov/codecov-action@v3 + with: + files: ./lcov.info + name: codecov-unit + fail_ci_if_error: true + verbose: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 06d00ee..8e2df48 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,25 +1,35 @@ name: lint -on: [push] +on: + push: + pull_request: + workflow_dispatch: env: - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: ci jobs: - check: - strategy: - fail-fast: true + check: + strategy: + fail-fast: true - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly - - name: Lint Check - run: forge fmt --check + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8ccf952..a3f3cf0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,33 +1,35 @@ name: test -on: [push] +on: + push: + pull_request: + workflow_dispatch: env: - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: ci jobs: - check: - strategy: - fail-fast: true + check: + strategy: + fail-fast: true - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly - - name: Build Project - run: | - forge --version - forge build --sizes - id: build + - name: Show Forge version + run: | + forge --version - - name: Test Project - run: | - forge test --fork-url ${{ secrets.BASE_RPC_URL }} --etherscan-api-key ${{ secrets.BASESCAN_API_KEY }} -vvv - id: test + - name: Run Forge tests + run: | + forge test -vvv + id: test From 13c0f16862c95d921c31f0ea4ab633005ce645bc Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 24 Sep 2024 10:54:39 -0400 Subject: [PATCH 034/129] =?UTF-8?q?=E2=9C=85=20update=20fork=20test=20setu?= =?UTF-8?q?p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Burn.t.sol | 3 +-- test/Flash.t.sol | 2 +- test/utils/Bootstrap.sol | 33 +++++++++++++++++++++++++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/test/Burn.t.sol b/test/Burn.t.sol index bb4f9e8..f83d633 100644 --- a/test/Burn.t.sol +++ b/test/Burn.t.sol @@ -15,8 +15,7 @@ import { contract BurnTest is Bootstrap { - function test_burn_arbitrum(uint32 amount) public arbitrum { - vm.rollFork(ARBITRUM_FORK_BLOCK_DEBT); + function test_burn_arbitrum(uint32 amount) public arbitrum_b { _spin(ARBITRUM_BOB, usdx, amount, address(zap)); vm.startPrank(ARBITRUM_BOB); core.grantPermission(ARBITRUM_BOB_ID, _BURN_PERMISSION, address(zap)); diff --git a/test/Flash.t.sol b/test/Flash.t.sol index 8b4af94..88dbd5d 100644 --- a/test/Flash.t.sol +++ b/test/Flash.t.sol @@ -20,7 +20,7 @@ contract FlashTest is Bootstrap { vm.rollFork(ARBITRUM_FORK_BLOCK_DEBT); _spin(ARBITRUM_BOB, usdx, amount, address(zap)); vm.startPrank(ARBITRUM_BOB); - + // 1. create perp market account // 2. approve usdx to perp market diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol index b9f87c9..35aa49a 100644 --- a/test/utils/Bootstrap.sol +++ b/test/utils/Bootstrap.sol @@ -16,7 +16,8 @@ import {Test} from "forge-std/Test.sol"; contract Bootstrap is Test, Base, Arbitrum, Constants { uint256 BASE; - uint256 ARBITRUM; + uint256 ARBITRUM_A; + uint256 ARBITRUM_B; Zap zap; @@ -33,8 +34,9 @@ contract Bootstrap is Test, Base, Arbitrum, Constants { string memory BASE_RPC = vm.envString(BASE_RPC_REF); string memory ARBITRUM_RPC = vm.envString(ARBITRUM_RPC_REF); - BASE = vm.createFork(BASE_RPC); - ARBITRUM = vm.createFork(ARBITRUM_RPC); + BASE = vm.createFork(BASE_RPC, BASE_FORK_BLOCK); + ARBITRUM_A = vm.createFork(ARBITRUM_RPC, ARBITRUM_FORK_BLOCK); + ARBITRUM_B = vm.createFork(ARBITRUM_RPC, ARBITRUM_FORK_BLOCK_DEBT); } modifier base() { @@ -61,7 +63,30 @@ contract Bootstrap is Test, Base, Arbitrum, Constants { } modifier arbitrum() { - vm.selectFork(ARBITRUM); + vm.selectFork(ARBITRUM_A); + zap = new Zap({ + _usdc: ARBITRUM_USDC, + _usdx: ARBITRUM_USDX, + _spotMarket: ARBITRUM_SPOT_MARKET, + _perpsMarket: ARBITRUM_PERPS_MARKET, + _core: ARBITRUM_CORE, + _referrer: ARBITRUM_REFERRER, + _susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, + _aave: ARBITRUM_AAVE_POOL, + _uniswap: ARBITRUM_UNISWAP + }); + core = ICore(ARBITRUM_CORE); + spotMarket = ISpotMarket(ARBITRUM_SPOT_MARKET); + perpsMarket = IPerpsMarket(ARBITRUM_PERPS_MARKET); + usdc = IERC20(ARBITRUM_USDC); + susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); + usdx = IERC20(ARBITRUM_USDX); + weth = IERC20(ARBITRUM_WETH); + _; + } + + modifier arbitrum_b() { + vm.selectFork(ARBITRUM_B); zap = new Zap({ _usdc: ARBITRUM_USDC, _usdx: ARBITRUM_USDX, From 8e6289fa8e5f21133ba76a52f5c3f01ec14e3554 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Wed, 25 Sep 2024 10:44:36 -0400 Subject: [PATCH 035/129] =?UTF-8?q?=E2=98=82=EF=B8=8F=20update=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/coverage.yml | 43 ---------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml deleted file mode 100644 index 841b124..0000000 --- a/.github/workflows/coverage.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: coverage - -on: - push: - pull_request: - workflow_dispatch: - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Show Forge version - run: | - forge --version - - - name: Run Forge coverage - run: | - forge coverage --report lcov - id: coverage - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: ./lcov.info - name: codecov-unit - fail_ci_if_error: true - verbose: true From 0f889def2376f4e17bf3ae13eb0b402592bcf0f5 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Wed, 25 Sep 2024 10:44:49 -0400 Subject: [PATCH 036/129] =?UTF-8?q?=E2=98=82=EF=B8=8F=20update=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index a3f3cf0..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: test - -on: - push: - pull_request: - workflow_dispatch: - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Show Forge version - run: | - forge --version - - - name: Run Forge tests - run: | - forge test -vvv - id: test From c8f26b3fcc4916d1199144ea4b06f4002e165128 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Wed, 25 Sep 2024 10:45:03 -0400 Subject: [PATCH 037/129] =?UTF-8?q?=F0=9F=91=B7=20update=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/ISynthetix.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/interfaces/ISynthetix.sol b/src/interfaces/ISynthetix.sol index 7d5af63..f262f49 100644 --- a/src/interfaces/ISynthetix.sol +++ b/src/interfaces/ISynthetix.sol @@ -206,6 +206,20 @@ interface IPerpsMarket { view returns (bool isAuthorized); + /** + * @notice Allows anyone to pay an account's debt + * @param accountId Id of the account. + * @param amount debt amount to pay off + */ + function payDebt(uint128 accountId, uint256 amount) external; + + /** + * @notice Returns account's debt + * @param accountId Id of the account. + * @return accountDebt specified account id's debt + */ + function debt(uint128 accountId) external view returns (uint256 accountDebt); + } interface ICore { From bba6c2d8c0d62cd1fcd777bc637bfdb1f103b77f Mon Sep 17 00:00:00 2001 From: jaredborders Date: Wed, 25 Sep 2024 10:45:23 -0400 Subject: [PATCH 038/129] =?UTF-8?q?=E2=9C=85=20remove=20test=20body?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Burn.t.sol | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/Burn.t.sol b/test/Burn.t.sol index f83d633..0dfdbca 100644 --- a/test/Burn.t.sol +++ b/test/Burn.t.sol @@ -15,12 +15,8 @@ import { contract BurnTest is Bootstrap { - function test_burn_arbitrum(uint32 amount) public arbitrum_b { - _spin(ARBITRUM_BOB, usdx, amount, address(zap)); - vm.startPrank(ARBITRUM_BOB); - core.grantPermission(ARBITRUM_BOB_ID, _BURN_PERMISSION, address(zap)); - zap.burn({_amount: amount, _accountId: ARBITRUM_BOB_ID}); - vm.stopPrank(); + function test_burn_arbitrum(uint32 amount) public { + /// @custom:todo arbitrum sepolia fork required } -} +} \ No newline at end of file From fbc5a14e6bdf2dcfa5dd6a34d1f4c619047f3296 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Wed, 25 Sep 2024 12:26:40 -0400 Subject: [PATCH 039/129] =?UTF-8?q?=F0=9F=91=B7=20refactor=20unwind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 184 +++++++++++++++++++++++----------- src/interfaces/ISynthetix.sol | 20 +++- src/interfaces/IUniswap.sol | 15 +++ 3 files changed, 158 insertions(+), 61 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 885e373..a743e31 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -281,34 +281,31 @@ contract Zap is Errors { } /*////////////////////////////////////////////////////////////// - AAVE + UNWIND COLLATERAL //////////////////////////////////////////////////////////////*/ - function requestFlashloan( - uint256 _usdcLoan, - uint256 _collateralAmount, - address _collateral, + /// @dev assumed USDC:USDx exchange rate is 1:1 + function unwind( uint128 _accountId, - uint128 _synthId, - uint256 _tolerance, + uint128 _collateralId, + uint256 _zapTolerance, uint256 _swapTolerance, - address receiver + address _receiver ) external { bytes memory params = abi.encode( - _collateralAmount, - _collateral, - _accountId, - _synthId, - _tolerance, - _swapTolerance, - receiver + _accountId, _collateralId, _zapTolerance, _swapTolerance, _receiver ); + + // determine amount of synthetix perp position debt to unwind; + // debt is denominated in USD + uint256 debt = IPerpsMarket(PERPS_MARKET).debt(_accountId); + IPool(AAVE).flashLoanSimple({ receiverAddress: address(this), asset: USDC, - amount: _usdcLoan, + amount: debt, params: params, referralCode: REFERRAL_CODE }); @@ -316,60 +313,80 @@ contract Zap is Errors { function executeOperation( address, - uint256 amount, - uint256 premium, + uint256 _flashloan, + uint256 _premium, address, - bytes calldata params + bytes calldata _params ) external returns (bool) { ( - uint256 collateralAmount, - address collateral, - uint128 accountId, - uint128 synthId, - uint256 tolerance, - uint256 swapTolerance, - address receiver - ) = abi.decode( - params, - (uint256, address, uint128, uint128, uint256, uint256, address) - ); - uint256 unwoundCollateral = _unwind( - amount, - collateralAmount, - collateral, - accountId, - synthId, - tolerance, - swapTolerance + uint128 _accountId, + uint128 _collateralId, + uint256 _zapTolerance, + uint256 _swapTolerance, + address _receiver + ) = abi.decode(_params, (uint128, uint128, uint256, uint256, address)); + + (uint256 unwound, address collateral) = _unwind( + _flashloan, + _premium, + _accountId, + _collateralId, + _zapTolerance, + _swapTolerance ); - uint256 debt = amount + premium; - uint256 differece = unwoundCollateral - debt; - IERC20(USDC).approve(AAVE, debt); - return IERC20(collateral).transfer(receiver, differece); + + _flashloan += _premium; + + IERC20(USDC).approve(AAVE, _flashloan); + return _push(collateral, _receiver, unwound); } function _unwind( - uint256 _usdcLoan, - uint256 _collateralAmount, - address _collateral, + uint256 _flashloan, + uint256 _premium, uint128 _accountId, - uint128 _synthId, - uint256 _tolerance, + uint128 _collateralId, + uint256 _zapTolerance, uint256 _swapTolerance ) internal - returns (uint256 unwound) + returns (uint256 unwound, address collateral) { - uint256 usdxAmount = _zapIn(_usdcLoan, _tolerance); + // zap USDC from flashloan into USDx + uint256 usdxAmount = _zapIn(_flashloan, _zapTolerance); + + // burn USDx to pay off synthetix perp position debt; + // debt is denominated in USD and thus repaid with USDx _burn(usdxAmount, _accountId); - _withdraw(_synthId, _collateralAmount, _accountId); - unwound = _unwrap(_synthId, _collateralAmount, _tolerance); - if (_synthId != SUSDC_SPOT_ID) { - _swap(_collateral, unwound, _swapTolerance); - } + + // determine amount of synthetix perp position collateral + // i.e., # of sETH, # of sUSDC, # of sUSDe, # of stBTC, etc. + uint256 collateralAmount = IPerpsMarket(PERPS_MARKET) + .getCollateralAmount(_accountId, _collateralId); + + // withdraw synthetix perp position collateral to this contract + _withdraw(_collateralId, collateralAmount, _accountId); + + // unwrap synthetix perp position collateral; + // i.e., sETH -> WETH, sUSDC -> USDC, etc. + uint256 unwrapped = + _unwrap(_collateralId, collateralAmount, _swapTolerance); + + // establish unwrapped collateral address + collateral = ISpotMarket(SPOT_MARKET).getSynth(_collateralId); + + // establish total debt now owed to Aave + _flashloan += _premium; + + // swap as necessary to repay Aave flashloan; + // only as much as necessary to repay the flashloan + uint256 deducted = _swapFor(collateral, _flashloan, _swapTolerance); + + // establish amount of unwound collateral after deduction + unwound = unwrapped - deducted; } /*////////////////////////////////////////////////////////////// @@ -381,11 +398,9 @@ contract Zap is Errors { _burn(_amount, _accountId); } - /// @custom:account permission required: "BURN" function _burn(uint256 _amount, uint128 _accountId) internal { IERC20(USDX).approve(CORE, _amount); - ICore(CORE).burnUsd(_accountId, PREFFERED_POOL_ID, USDC, _amount); - ICore(CORE).renouncePermission(_accountId, BURN_PERMISSION); + IPerpsMarket(PERPS_MARKET).payDebt(_accountId, _amount); } /*////////////////////////////////////////////////////////////// @@ -428,7 +443,55 @@ contract Zap is Errors { UNISWAP //////////////////////////////////////////////////////////////*/ - function swap( + function swapFor( + address _from, + uint256 _amount, + uint256 _tolerance, + address _receiver + ) + external + returns (uint256 deducted) + { + _pull(_from, msg.sender, _tolerance); + deducted = _swapFor(_from, _amount, _tolerance); + _push(USDC, _receiver, _amount); + + if (deducted < _tolerance) { + _push(_from, msg.sender, _tolerance - deducted); + } + } + + function _swapFor( + address _from, + uint256 _amount, + uint256 _tolerance + ) + internal + returns (uint256 deducted) + { + IERC20(_from).approve(UNISWAP, _tolerance); + + IUniswap.ExactOutputSingleParams memory params = IUniswap + .ExactOutputSingleParams({ + tokenIn: _from, + tokenOut: USDC, + fee: FEE_TIER, + recipient: address(this), + amountOut: _amount, + amountInMaximum: _tolerance, + sqrtPriceLimitX96: 0 + }); + + try IUniswap(UNISWAP).exactOutputSingle(params) returns ( + uint256 amountIn + ) { + deducted = amountIn; + } catch Error(string memory reason) { + revert SwapFailed(reason); + } + } + + function swapWith( address _from, uint256 _amount, uint256 _tolerance, @@ -438,11 +501,11 @@ contract Zap is Errors { returns (uint256 received) { _pull(_from, msg.sender, _amount); - received = _swap(_from, _amount, _tolerance); + received = _swapWith(_from, _amount, _tolerance); _push(USDC, _receiver, received); } - function _swap( + function _swapWith( address _from, uint256 _amount, uint256 _tolerance @@ -451,6 +514,7 @@ contract Zap is Errors { returns (uint256 received) { IERC20(_from).approve(UNISWAP, _amount); + IUniswap.ExactInputSingleParams memory params = IUniswap .ExactInputSingleParams({ tokenIn: _from, diff --git a/src/interfaces/ISynthetix.sol b/src/interfaces/ISynthetix.sol index f262f49..f55310e 100644 --- a/src/interfaces/ISynthetix.sol +++ b/src/interfaces/ISynthetix.sol @@ -218,7 +218,25 @@ interface IPerpsMarket { * @param accountId Id of the account. * @return accountDebt specified account id's debt */ - function debt(uint128 accountId) external view returns (uint256 accountDebt); + function debt(uint128 accountId) + external + view + returns (uint256 accountDebt); + + /** + * @notice Gets the account's collateral value for a specific collateral. + * @param accountId Id of the account. + * @param collateralId Id of the synth market used as collateral. Synth + * market id, 0 for snxUSD. + * @return collateralValue collateral value of the account. + */ + function getCollateralAmount( + uint128 accountId, + uint128 collateralId + ) + external + view + returns (uint256); } diff --git a/src/interfaces/IUniswap.sol b/src/interfaces/IUniswap.sol index f30d281..847b0ae 100644 --- a/src/interfaces/IUniswap.sol +++ b/src/interfaces/IUniswap.sol @@ -19,4 +19,19 @@ interface IUniswap { payable returns (uint256 amountOut); + struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; + } + + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + returns (uint256 amountIn); + } From e80d577456df8e042f54095ea50bac65ab1c0854 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Wed, 25 Sep 2024 12:27:18 -0400 Subject: [PATCH 040/129] =?UTF-8?q?=E2=9C=85=20refactor=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Burn.t.sol | 7 +++--- test/Flash.t.sol | 61 ++++++++++++++++++++++++++++++++++-------------- test/Swap.t.sol | 44 ++++++++-------------------------- 3 files changed, 57 insertions(+), 55 deletions(-) diff --git a/test/Burn.t.sol b/test/Burn.t.sol index 0dfdbca..4b9f4eb 100644 --- a/test/Burn.t.sol +++ b/test/Burn.t.sol @@ -15,8 +15,7 @@ import { contract BurnTest is Bootstrap { - function test_burn_arbitrum(uint32 amount) public { - /// @custom:todo arbitrum sepolia fork required - } + /// @custom:todo on arbitrum sepolia fork + function test_burn_arbitrum_sepolia(uint32 amount) public {} -} \ No newline at end of file +} diff --git a/test/Flash.t.sol b/test/Flash.t.sol index 88dbd5d..fe9b014 100644 --- a/test/Flash.t.sol +++ b/test/Flash.t.sol @@ -15,23 +15,50 @@ import { contract FlashTest is Bootstrap { - /// @custom:todo - function test_flash_arbitrum(uint32 amount) public arbitrum { - vm.rollFork(ARBITRUM_FORK_BLOCK_DEBT); - _spin(ARBITRUM_BOB, usdx, amount, address(zap)); - vm.startPrank(ARBITRUM_BOB); - - // 1. create perp market account - - // 2. approve usdx to perp market - - // 3. grant zap permission to modify collateral - - // 4. modify collateral; add margin - - // 5. request flash loan - - vm.stopPrank(); + /// @custom:todo on arbitrum sepolia fork + function test_flash_arbitrum_sepolia() public { + /** + * // 0. define bob; + * vm.startPrank(ARBITRUM_BOB); + * + * // 1. define amount; 1000 usdx == $1000 + * uint128 amount = 1000 ether; + * + * // 2. spin up bob with 1000 usdx + * _spin(ARBITRUM_BOB, usdx, amount, address(zap)); + * + * // 3. create perp market account + * uint128 accountId = perpsMarket.createAccount(); + * + * // 4. grant perp market usdx allowance + * usdx.approve(address(perpsMarket), type(uint256).max); + * + * // 5. grant zap permission to modify an account's collateral + * perpsMarket.grantPermission( + * accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + * ); + * + * // 6. add 1000 usdx as collateral + * perpsMarket.modifyCollateral(accountId, 0, int128(amount)); + * + * // 7. determine debt + * uint256 accountDebt = perpsMarket.debt(accountId); + * + * // 8. request flash loan + * zap.requestFlashloan({ + * _usdcLoan: accountDebt, + * _collateralAmount: amount, + * _collateral: address(usdc), + * _accountId: accountId, + * _synthId: ARBITRUM_SUSDC_SPOT_MARKET_ID, + * _tolerance: DEFAULT_TOLERANCE, + * _swapTolerance: DEFAULT_TOLERANCE, + * receiver: ARBITRUM_BOB + * }); + * + * // 9. end bob prank + * vm.stopPrank(); + */ } } diff --git a/test/Swap.t.sol b/test/Swap.t.sol index 0cd2a86..8e86cfc 100644 --- a/test/Swap.t.sol +++ b/test/Swap.t.sol @@ -15,40 +15,16 @@ import { contract SwapTest is Bootstrap { - function test_swap_base() public base { - uint256 amount = 1 ether; - _spin(ACTOR, weth, amount, address(zap)); - assertEq(weth.balanceOf(ACTOR), amount); - assertEq(usdc.balanceOf(ACTOR), 0); - vm.startPrank(ACTOR); - uint256 received = zap.swap({ - _from: address(weth), - _amount: amount, - _tolerance: DEFAULT_TOLERANCE, - _receiver: ACTOR - }); - vm.stopPrank(); - assertEq(weth.balanceOf(ACTOR), 0); - assertEq(usdc.balanceOf(ACTOR), received); - assertTrue(received > 0); - } + /// @custom:todo + function test_swap_for_base() public base {} - function test_swap_arbitrum() public arbitrum { - uint256 amount = 1 ether; - _spin(ACTOR, weth, amount, address(zap)); - assertEq(weth.balanceOf(ACTOR), amount); - assertEq(usdc.balanceOf(ACTOR), 0); - vm.startPrank(ACTOR); - uint256 received = zap.swap({ - _from: address(weth), - _amount: amount, - _tolerance: DEFAULT_TOLERANCE, - _receiver: ACTOR - }); - vm.stopPrank(); - assertEq(weth.balanceOf(ACTOR), 0); - assertEq(usdc.balanceOf(ACTOR), received); - assertTrue(received > 0); - } + /// @custom:todo + function test_swap_with_base() public base {} + + /// @custom:todo + function test_swap_for_arbitrum() public arbitrum {} + + /// @custom:todo + function test_swap_with_arbitrum() public arbitrum {} } From ddfdbcfb5ebba197a025147449ef8682845e0b57 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Wed, 25 Sep 2024 13:08:51 -0400 Subject: [PATCH 041/129] =?UTF-8?q?=E2=9C=A8=20prettify?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Zap.sol b/src/Zap.sol index a743e31..5a1447b 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -339,7 +339,7 @@ contract Zap is Errors { ); _flashloan += _premium; - + IERC20(USDC).approve(AAVE, _flashloan); return _push(collateral, _receiver, unwound); } From 5f7c2d9d32a6eedf543990526eb6e895b4c88fc0 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Wed, 25 Sep 2024 14:13:50 -0400 Subject: [PATCH 042/129] =?UTF-8?q?=F0=9F=91=B7=20polish=20zap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 147 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 133 insertions(+), 14 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 5a1447b..7a82b3c 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -76,6 +76,12 @@ contract Zap is Errors { ZAP //////////////////////////////////////////////////////////////*/ + /// @notice zap USDC into USDx + /// @dev caller must grant USDC allowance to this contract + /// @param _amount amount of USDC to zap + /// @param _tolerance acceptable slippage for wrapping and selling + /// @param _receiver address to receive USDx + /// @return zapped amount of USDx received function zapIn( uint256 _amount, uint256 _tolerance, @@ -89,6 +95,8 @@ contract Zap is Errors { _push(USDX, _receiver, zapped); } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold the zapped USDx function _zapIn( uint256 _amount, uint256 _tolerance @@ -100,6 +108,12 @@ contract Zap is Errors { zapped = _sell(SUSDC_SPOT_ID, zapped, _tolerance); } + /// @notice zap USDx into USDC + /// @dev caller must grant USDx allowance to this contract + /// @param _amount amount of USDx to zap + /// @param _tolerance acceptable slippage for buying and unwrapping + /// @param _receiver address to receive USDC + /// @return zapped amount of USDC received function zapOut( uint256 _amount, uint256 _tolerance, @@ -113,6 +127,8 @@ contract Zap is Errors { _push(USDC, _receiver, zapped); } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold the zapped USDC function _zapOut( uint256 _amount, uint256 _tolerance @@ -128,6 +144,15 @@ contract Zap is Errors { WRAP AND UNWRAP //////////////////////////////////////////////////////////////*/ + /// @notice wrap collateral via synthetix spot market + /// @dev caller must grant token allowance to this contract + /// @custom:synth -> synthetix token representation of wrapped collateral + /// @param _token address of token to wrap + /// @param _synthId synthetix market id of synth to wrap into + /// @param _amount amount of token to wrap + /// @param _tolerance acceptable slippage for wrapping + /// @param _receiver address to receive wrapped synth + /// @return wrapped amount of synth received function wrap( address _token, uint128 _synthId, @@ -140,10 +165,11 @@ contract Zap is Errors { { _pull(_token, msg.sender, _amount); wrapped = _wrap(_token, _synthId, _amount, _tolerance); - address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); - _push(synth, _receiver, wrapped); + _push(ISpotMarket(SPOT_MARKET).getSynth(_synthId), _receiver, wrapped); } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold the wrapped synth function _wrap( address _token, uint128 _synthId, @@ -154,7 +180,6 @@ contract Zap is Errors { returns (uint256 wrapped) { IERC20(_token).approve(SPOT_MARKET, _amount); - try ISpotMarket(SPOT_MARKET).wrap({ marketId: _synthId, wrapAmount: _amount, @@ -166,6 +191,15 @@ contract Zap is Errors { } } + /// @notice unwrap collateral via synthetix spot market + /// @dev caller must grant synth allowance to this contract + /// @custom:synth -> synthetix token representation of wrapped collateral + /// @param _token address of token to unwrap into + /// @param _synthId synthetix market id of synth to unwrap + /// @param _amount amount of synth to unwrap + /// @param _tolerance acceptable slippage for unwrapping + /// @param _receiver address to receive unwrapped token + /// @return unwrapped amount of token received function unwrap( address _token, uint128 _synthId, @@ -182,6 +216,8 @@ contract Zap is Errors { _push(_token, _receiver, unwrapped); } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold the unwrapped token function _unwrap( uint128 _synthId, uint256 _amount, @@ -207,6 +243,13 @@ contract Zap is Errors { BUY AND SELL //////////////////////////////////////////////////////////////*/ + /// @notice buy synth via synthetix spot market + /// @dev caller must grant USDX allowance to this contract + /// @param _synthId synthetix market id of synth to buy + /// @param _amount amount of USDX to spend + /// @param _tolerance acceptable slippage for buying + /// @param _receiver address to receive synth + /// @return received amount of synth function buy( uint128 _synthId, uint256 _amount, @@ -221,6 +264,8 @@ contract Zap is Errors { _push(synth, _receiver, received); } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold the bought synth function _buy( uint128 _synthId, uint256 _amount, @@ -243,6 +288,13 @@ contract Zap is Errors { } } + /// @notice sell synth via synthetix spot market + /// @dev caller must grant synth allowance to this contract + /// @param _synthId synthetix market id of synth to sell + /// @param _amount amount of synth to sell + /// @param _tolerance acceptable slippage for selling + /// @param _receiver address to receive USDX + /// @return received amount of USDX function sell( uint128 _synthId, uint256 _amount, @@ -258,6 +310,8 @@ contract Zap is Errors { _push(USDX, _receiver, received); } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold the sold USDX function _sell( uint128 _synthId, uint256 _amount, @@ -284,7 +338,14 @@ contract Zap is Errors { UNWIND COLLATERAL //////////////////////////////////////////////////////////////*/ - /// @dev assumed USDC:USDx exchange rate is 1:1 + /// @notice unwind synthetix perp position collateral + /// @dev caller must grant USDC allowance to this contract + /// @custom:synthetix RBAC permission required: "PERPS_MODIFY_COLLATERAL" + /// @param _accountId synthetix perp market account id + /// @param _collateralId synthetix market id of collateral + /// @param _zapTolerance acceptable slippage for zapping + /// @param _swapTolerance acceptable slippage for swapping + /// @param _receiver address to receive unwound collateral function unwind( uint128 _accountId, uint128 _collateralId, @@ -311,6 +372,12 @@ contract Zap is Errors { }); } + /// @notice flashloan callback function + /// @dev caller is expected to be the Aave lending pool + /// @custom:caution calling this function directly is not recommended + /// @param _flashloan amount of USDC flashloaned from Aave + /// @param _premium amount of USDC premium owed to Aave + /// @return bool representing successful execution function executeOperation( address, uint256 _flashloan, @@ -344,6 +411,15 @@ contract Zap is Errors { return _push(collateral, _receiver, unwound); } + /// @dev unwinds synthetix perp position collateral + /// @param _flashloan amount of USDC flashloaned from Aave + /// @param _premium amount of USDC premium owed to Aave + /// @param _accountId synthetix perp market account id + /// @param _collateralId synthetix market id of collateral + /// @param _zapTolerance acceptable slippage for zapping + /// @param _swapTolerance acceptable slippage for swapping + /// @return unwound amount of collateral + /// @return collateral address function _unwind( uint256 _flashloan, uint256 _premium, @@ -393,11 +469,21 @@ contract Zap is Errors { BURN //////////////////////////////////////////////////////////////*/ + /// @notice burn USDx to pay off synthetix perp position debt + /// @custom:caution ALL USDx remaining post-burn will be sent to the caller + /// @dev caller must grant USDX allowance to this contract + /// @dev excess USDx will be returned to the caller + /// @param _amount amount of USDx to burn + /// @param _accountId synthetix perp market account id function burn(uint256 _amount, uint128 _accountId) external { _pull(USDX, msg.sender, _amount); _burn(_amount, _accountId); + uint256 remaining = IERC20(USDX).balanceOf(address(this)); + _push(USDX, msg.sender, remaining); } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold any excess USDx function _burn(uint256 _amount, uint128 _accountId) internal { IERC20(USDX).approve(CORE, _amount); IPerpsMarket(PERPS_MARKET).payDebt(_accountId, _amount); @@ -407,6 +493,12 @@ contract Zap is Errors { WITHDRAW //////////////////////////////////////////////////////////////*/ + /// @notice withdraw collateral from synthetix perp position + /// @custom:synthetix RBAC permission required: "PERPS_MODIFY_COLLATERAL" + /// @param _synthId synthetix market id of collateral + /// @param _amount amount of collateral to withdraw + /// @param _accountId synthetix perp market account id + /// @param _receiver address to receive collateral function withdraw( uint128 _synthId, uint256 _amount, @@ -422,7 +514,9 @@ contract Zap is Errors { _push(synth, _receiver, _amount); } - /// @custom:account permission required: "PERPS_MODIFY_COLLATERAL" + /// @custom:synthetix RBAC permission required: "PERPS_MODIFY_COLLATERAL" + /// @dev following execution, this contract will hold the withdrawn + /// collateral function _withdraw( uint128 _synthId, uint256 _amount, @@ -443,6 +537,14 @@ contract Zap is Errors { UNISWAP //////////////////////////////////////////////////////////////*/ + /// @notice swap a tolerable amount of tokens for a specific amount of USDC + /// @dev caller must grant token allowance to this contract + /// @dev any excess token not spent will be returned to the caller + /// @param _from address of token to swap + /// @param _amount of USDC to receive in return + /// @param _tolerance or tolerable amount of token to spend + /// @param _receiver address to receive USDC + /// @return deducted amount of incoming token; i.e., amount spent function swapFor( address _from, uint256 _amount, @@ -461,6 +563,8 @@ contract Zap is Errors { } } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold the swapped USDC function _swapFor( address _from, uint256 _amount, @@ -491,6 +595,13 @@ contract Zap is Errors { } } + /// @notice swap a specific amount of tokens for a tolerable amount of USDC + /// @dev caller must grant token allowance to this contract + /// @param _from address of token to swap + /// @param _amount of token to swap + /// @param _tolerance or tolerable amount of USDC to receive + /// @param _receiver address to receive USDC + /// @return received amount of USDC function swapWith( address _from, uint256 _amount, @@ -505,6 +616,8 @@ contract Zap is Errors { _push(USDC, _receiver, received); } + /// @dev allowance is assumed + /// @dev following execution, this contract will hold the swapped USDC function _swapWith( address _from, uint256 _amount, @@ -539,32 +652,38 @@ contract Zap is Errors { TRANSFERS //////////////////////////////////////////////////////////////*/ + /// @dev pull tokens from a sender + /// @param _token address of token to pull + /// @param _from address of sender + /// @param _amount amount of token to pull + /// @return bool representing successful execution function _pull( address _token, address _from, uint256 _amount ) internal - returns (bool success) + returns (bool) { IERC20 token = IERC20(_token); - success = token.transferFrom(_from, address(this), _amount); + return token.transferFrom(_from, address(this), _amount); } + /// @dev push tokens to a receiver + /// @param _token address of token to push + /// @param _receiver address of receiver + /// @param _amount amount of token to push + /// @return bool representing successful execution function _push( address _token, address _receiver, uint256 _amount ) internal - returns (bool success) + returns (bool) { - if (_receiver == address(this)) { - success = true; - } else { - IERC20 token = IERC20(_token); - success = token.transfer(_receiver, _amount); - } + IERC20 token = IERC20(_token); + return token.transfer(_receiver, _amount); } } From f1b7182d376f603e5a270c5ebee7dad997bfdff0 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Thu, 26 Sep 2024 15:38:55 +0200 Subject: [PATCH 043/129] =?UTF-8?q?=F0=9F=91=B7=20add=20arbitrum=20sepolia?= =?UTF-8?q?=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/utils/Parameters.sol | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/script/utils/Parameters.sol b/script/utils/Parameters.sol index 81590be..84b14fb 100644 --- a/script/utils/Parameters.sol +++ b/script/utils/Parameters.sol @@ -38,3 +38,26 @@ contract Arbitrum { address ARBITRUM_UNISWAP = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; } + +contract ArbitrumSepolia { + + /// @custom:synthetix + address ARBITRUM_SEPOLIA_USDC = 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d; + address ARBITRUM_SEPOLIA_USDX = 0xb645d694D424D091Aa5BA893Eafe85b381bd82Eb; + address ARBITRUM_SEPOLIA_SPOT_MARKET = + 0x93d645c42A0CA3e08E9552367B8c454765fff041; + address ARBITRUM_SEPOLIA_PERPS_MARKET = + 0xA73A7B754Ec870b3738D0654cA75b7d0eEbdb460; + address ARBITRUM_SEPOLIA_CORE = 0x76490713314fCEC173f44e99346F54c6e92a8E42; + address ARBITRUM_SEPOLIA_REFERRER = address(0); + uint128 ARBITRUM_SEPOLIAS_USDC_SPOT_MARKET_ID = 2; + + /// @custom:aave + address ARBITRUM_SEPOLIA_AAVE_POOL = + 0xBfC91D59fdAA134A4ED45f7B584cAf96D7792Eff; + + /// @custom:uniswap + address ARBITRUM_SEPOLIA_UNISWAP = + 0x101F443B4d1b059569D643917553c771E1b9663E; + +} From adac7d8183cac7b16b8c82dc0d623d7e8e9ed0ab Mon Sep 17 00:00:00 2001 From: Flocqst Date: Thu, 26 Sep 2024 15:39:48 +0200 Subject: [PATCH 044/129] =?UTF-8?q?=F0=9F=9A=80=20implement=20deploySystem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 88 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index cfd330e..1f878a0 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -3,14 +3,38 @@ pragma solidity 0.8.27; import {Script} from "../lib/forge-std/src/Script.sol"; import {Zap} from "../src/Zap.sol"; +import {Arbitrum, ArbitrumSepolia, Base} from "./utils/Parameters.sol"; /// @title Zap deployment script /// @author JaredBorders (jaredborders@pm.me) contract Setup is Script { /// @custom:todo - function deploySystem() public returns (address zap) { - // zap = address(new Zap()); + function deploySystem( + address usdc, + address usdx, + address spotMarket, + address perpsMarket, + address core, + address referrer, + uint128 susdcSpotId, + address aave, + address uniswap + ) + public + returns (Zap zap) + { + zap = new Zap({ + _usdc: usdc, + _usdx: usdx, + _spotMarket: spotMarket, + _perpsMarket: perpsMarket, + _core: core, + _referrer: referrer, + _susdcSpotId: susdcSpotId, + _aave: aave, + _uniswap: uniswap + }); } } @@ -19,13 +43,23 @@ contract Setup is Script { /// (1) load the variables in the .env file via `source .env` /// (2) run `forge script script/Deploy.s.sol:DeployBase --rpc-url $BASE_RPC_URL /// --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv` -contract DeployBase is Setup { +contract DeployBase is Setup, Base { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - Setup.deploySystem(); + Setup.deploySystem({ + usdc: BASE_USDC, + usdx: BASE_USDX, + spotMarket: BASE_SPOT_MARKET, + perpsMarket: BASE_PERPS_MARKET, + core: BASE_CORE, + referrer: BASE_REFERRER, + susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, + aave: BASE_AAVE_POOL, + uniswap: BASE_UNISWAP + }); vm.stopBroadcast(); } @@ -35,15 +69,53 @@ contract DeployBase is Setup { /// @dev steps to deploy and verify on Arbitrum: /// (1) load the variables in the .env file via `source .env` /// (2) run `forge script script/Deploy.s.sol:DeployArbitrum --rpc-url -/// $ARBITRUM_RPC_URL --etherscan-api-key $ARBITRUM_RPC_URL -/// --broadcast --verify -vvvv` -contract DeployArbitrum is Setup { +/// $ARBITRUM_RPC_URL +/// --etherscan-api-key $ARBITRUM_RPC_URL --broadcast --verify -vvvv` +contract DeployArbitrum is Setup, Arbitrum { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - Setup.deploySystem(); + Setup.deploySystem({ + usdc: ARBITRUM_USDC, + usdx: ARBITRUM_USDX, + spotMarket: ARBITRUM_SPOT_MARKET, + perpsMarket: ARBITRUM_PERPS_MARKET, + core: ARBITRUM_CORE, + referrer: ARBITRUM_REFERRER, + susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, + aave: ARBITRUM_AAVE_POOL, + uniswap: ARBITRUM_UNISWAP + }); + + vm.stopBroadcast(); + } + +} + +/// @dev steps to deploy and verify on Arbitrum: +/// (1) load the variables in the .env file via `source .env` +/// (2) run `forge script script/Deploy.s.sol:DeployArbitrumSepolia --rpc-url +/// $ARBITRUM_SEPOLIA_RPC_URL +/// --etherscan-api-key $ARBITRUM_RPC_URL --broadcast --verify -vvvv` +contract DeployArbitrumSepolia is Setup, ArbitrumSepolia { + + function run() public { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(privateKey); + + Setup.deploySystem({ + usdc: ARBITRUM_SEPOLIA_USDC, + usdx: ARBITRUM_SEPOLIA_USDX, + spotMarket: ARBITRUM_SEPOLIA_SPOT_MARKET, + perpsMarket: ARBITRUM_SEPOLIA_PERPS_MARKET, + core: ARBITRUM_SEPOLIA_CORE, + referrer: ARBITRUM_SEPOLIA_REFERRER, + susdcSpotId: ARBITRUM_SEPOLIAS_USDC_SPOT_MARKET_ID, + aave: ARBITRUM_SEPOLIA_AAVE_POOL, + uniswap: ARBITRUM_SEPOLIA_UNISWAP + }); vm.stopBroadcast(); } From 93fc895fce808916ca01f078296465641719d6ed Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 26 Sep 2024 16:27:44 -0400 Subject: [PATCH 045/129] H-2 --- src/Zap.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Zap.sol b/src/Zap.sol index 7a82b3c..c890b35 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -355,6 +355,11 @@ contract Zap is Errors { ) external { + IPerpsMarket market = IPerpsMarket(PERPS_MARKET); + if (!market.hasPermission(_accountId, MODIFY_PERMISSION, msg.sender)) { + revert Errors.NotPermitted(); + } + bytes memory params = abi.encode( _accountId, _collateralId, _zapTolerance, _swapTolerance, _receiver ); From 718ee9738191ac7f54400dc88b347543ebc809ef Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 26 Sep 2024 16:33:42 -0400 Subject: [PATCH 046/129] added author --- src/Zap.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Zap.sol b/src/Zap.sol index 7a82b3c..1cf0965 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -16,6 +16,7 @@ import {IUniswap} from "./interfaces/IUniswap.sol"; /// @author @jaredborders /// @author @barrasso /// @author @Flocqst +/// @author @moss-eth contract Zap is Errors { /// @custom:synthetix From 8a9af4dac114f847a404acea5bc62cdc491ed599 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 26 Sep 2024 17:03:33 -0400 Subject: [PATCH 047/129] safe transfer --- src/Zap.sol | 6 +- src/interfaces/IERC20.sol | 122 +++++++++++++++++++++++++------------- 2 files changed, 86 insertions(+), 42 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 1cf0965..f1e19b2 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -667,7 +667,8 @@ contract Zap is Errors { returns (bool) { IERC20 token = IERC20(_token); - return token.transferFrom(_from, address(this), _amount); + token.safeTransferFrom(token, _from, address(this), _amount); + return true; } /// @dev push tokens to a receiver @@ -684,7 +685,8 @@ contract Zap is Errors { returns (bool) { IERC20 token = IERC20(_token); - return token.transfer(_receiver, _amount); + token.safeTransfer(token, _receiver, _amount); + return true; } } diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index cc89afe..f0e32b8 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -1,47 +1,89 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.27; +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) -/// @title Reduced Interface of the ERC20 standard as defined in the EIP -/// @author OpenZeppelin +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC-20 standard as defined in the ERC. + * @author OpenZeppelin + */ interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); - /// @dev Returns the number of decimals used to get its user representation. - /// For example, if `decimals` equals `2`, a balance of `505` tokens should - /// be displayed to a user as `5.05` (`505 / 10 ** 2`). - function decimals() external view returns (uint8); + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); - /// @dev Returns the amount of tokens owned by `account`. + /** + * @dev Returns the value of tokens owned by `account`. + */ function balanceOf(address account) external view returns (uint256); - /// @dev Moves `amount` tokens from the caller's account to `to` - /// @param to The address of the recipient - /// @param amount The amount of tokens to transfer - /// @return a boolean value indicating whether the operation succeeded - /// Emits a {Transfer} event - function transfer(address to, uint256 amount) external returns (bool); - - /// @dev Sets `amount` as the allowance of `spender` over the caller's - /// tokens - /// @param spender The address of the account to grant the allowance - /// @param amount The amount of tokens to allow - /// @return a boolean value indicating whether the operation succeeded - /// Emits an {Approval} event. - function approve(address spender, uint256 amount) external returns (bool); - - /// @dev Moves `amount` tokens from `from` to `to` using the - /// allowance mechanism. `amount` is then deducted from the caller's - /// allowance - /// @param from The address of the sender - /// @param to The address of the recipient - /// @param amount The amount of tokens to transfer - /// @return a boolean value indicating whether the operation succeeded. - /// Emits a {Transfer} event - function transferFrom( - address from, - address to, - uint256 amount - ) - external - returns (bool); - -} + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); + + /** + * expected to revert if any errors are thrown during transfer + */ + function safeTransfer(IERC20 token, address to, uint256 value) external; + /** + * expected to revert if any errors are thrown during transfer + */ + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) external; +} \ No newline at end of file From 6039de7149633a0987bfa49c73a0d1430689ec45 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 26 Sep 2024 17:19:27 -0400 Subject: [PATCH 048/129] comments --- src/interfaces/IERC20.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index f0e32b8..334ebe3 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -79,11 +79,13 @@ interface IERC20 { function transferFrom(address from, address to, uint256 value) external returns (bool); /** - * expected to revert if any errors are thrown during transfer + * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) external; /** - * expected to revert if any errors are thrown during transfer + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) external; } \ No newline at end of file From d5323db7133bfb985d36b8db9c124d438cd34293 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 26 Sep 2024 17:20:16 -0400 Subject: [PATCH 049/129] fmt --- src/interfaces/IERC20.sol | 47 ++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 334ebe3..78fec06 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -8,6 +8,7 @@ pragma solidity ^0.8.20; * @author OpenZeppelin */ interface IERC20 { + /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). @@ -20,7 +21,9 @@ interface IERC20 { * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ - event Approval(address indexed owner, address indexed spender, uint256 value); + event Approval( + address indexed owner, address indexed spender, uint256 value + ); /** * @dev Returns the value of tokens in existence. @@ -48,15 +51,23 @@ interface IERC20 { * * This value changes when {approve} or {transferFrom} are called. */ - function allowance(address owner, address spender) external view returns (uint256); + function allowance( + address owner, + address spender + ) + external + view + returns (uint256); /** - * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * @dev Sets a `value` amount of tokens as the allowance of `spender` over + * the * caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * - * IMPORTANT: Beware that changing an allowance with this method brings the risk + * IMPORTANT: Beware that changing an allowance with this method brings the + * risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the @@ -76,16 +87,32 @@ interface IERC20 { * * Emits a {Transfer} event. */ - function transferFrom(address from, address to, uint256 value) external returns (bool); + function transferFrom( + address from, + address to, + uint256 value + ) + external + returns (bool); /** - * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * @dev Transfer `value` amount of `token` from the calling contract to + * `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) external; /** - * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the - * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the + * approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are + * assumed to be successful. */ - function safeTransferFrom(IERC20 token, address from, address to, uint256 value) external; -} \ No newline at end of file + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) + external; + +} From 1bcc8b71d5728f2d15e95e351102a4d5f6aef150 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 26 Sep 2024 17:23:18 -0400 Subject: [PATCH 050/129] M-4 only aave can call exeOp --- src/Zap.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Zap.sol b/src/Zap.sol index 1cf0965..fcdb041 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -389,6 +389,10 @@ contract Zap is Errors { external returns (bool) { + if (msg.sender != AAVE) { + revert Errors.NotPermitted(); + } + ( uint128 _accountId, uint128 _collateralId, From a51adc9f62916fde6e57402494e2d984a3b6db36 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 27 Sep 2024 09:26:02 -0400 Subject: [PATCH 051/129] added reentrancyGuard --- src/Enums.sol | 15 +++++++++++++++ src/Errors.sol | 3 +++ src/Zap.sol | 24 +++++++++++++++++++++--- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/Enums.sol diff --git a/src/Enums.sol b/src/Enums.sol new file mode 100644 index 0000000..9d14ebe --- /dev/null +++ b/src/Enums.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +/// @title zap enums +/// @author @moss-eth +contract Enums { + + /// @notice multi-level reentrancy guard to protect callback function + enum MultiLevelReentrancyGuard { + Unset, + Level1, + Level2 + } + +} diff --git a/src/Errors.sol b/src/Errors.sol index 07986a5..7848b52 100644 --- a/src/Errors.sol +++ b/src/Errors.sol @@ -28,4 +28,7 @@ contract Errors { /// @notice thrown when operation is not permitted error NotPermitted(); + /// @notice Unauthorized reentrant call. + error ReentrancyGuardReentrantCall(); + } diff --git a/src/Zap.sol b/src/Zap.sol index 1cf0965..398de87 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; +import {Enums} from "./Enums.sol"; import {Errors} from "./Errors.sol"; import {IPool} from "./interfaces/IAave.sol"; import {IERC20} from "./interfaces/IERC20.sol"; @@ -17,7 +18,7 @@ import {IUniswap} from "./interfaces/IUniswap.sol"; /// @author @barrasso /// @author @Flocqst /// @author @moss-eth -contract Zap is Errors { +contract Zap is Enums, Errors { /// @custom:synthetix address public immutable USDC; @@ -40,6 +41,9 @@ contract Zap is Errors { address public immutable UNISWAP; uint24 public immutable FEE_TIER; + /// @dev set to `Unset` by default + MultiLevelReentrancyGuard reentrancyGuard; + constructor( address _usdc, address _usdx, @@ -356,6 +360,12 @@ contract Zap is Errors { ) external { + if (reentrancyGuard != MultiLevelReentrancyGuard.Unset) { + revert ReentrancyGuardReentrantCall(); + } + + reentrancyGuard = MultiLevelReentrancyGuard.Level1; + bytes memory params = abi.encode( _accountId, _collateralId, _zapTolerance, _swapTolerance, _receiver ); @@ -389,6 +399,11 @@ contract Zap is Errors { external returns (bool) { + if (reentrancyGuard != MultiLevelReentrancyGuard.Level1) { + revert ReentrancyGuardReentrantCall(); + } + reentrancyGuard = MultiLevelReentrancyGuard.Level2; + ( uint128 _accountId, uint128 _collateralId, @@ -409,6 +424,9 @@ contract Zap is Errors { _flashloan += _premium; IERC20(USDC).approve(AAVE, _flashloan); + + reentrancyGuard = MultiLevelReentrancyGuard.Unset; + return _push(collateral, _receiver, unwound); } @@ -681,10 +699,10 @@ contract Zap is Errors { uint256 _amount ) internal - returns (bool) + returns (bool success) { IERC20 token = IERC20(_token); - return token.transfer(_receiver, _amount); + success = token.transfer(_receiver, _amount); } } From 912e1a7e71ceef4895f24b5eeff5c8600e37527f Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 27 Sep 2024 09:42:57 -0400 Subject: [PATCH 052/129] M-6 --- src/Zap.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 1cf0965..26ede08 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -361,13 +361,16 @@ contract Zap is Errors { ); // determine amount of synthetix perp position debt to unwind; - // debt is denominated in USD + // debt is denominated in USD with 18 decimals + // the Aave USDC pool uses USDC token amounts denominated with 6 + // decimals uint256 debt = IPerpsMarket(PERPS_MARKET).debt(_accountId); + uint256 debt6Decimals = debt / (10 ** (18 - 6)); IPool(AAVE).flashLoanSimple({ receiverAddress: address(this), asset: USDC, - amount: debt, + amount: debt6Decimals, params: params, referralCode: REFERRAL_CODE }); @@ -542,7 +545,7 @@ contract Zap is Errors { /// @dev caller must grant token allowance to this contract /// @dev any excess token not spent will be returned to the caller /// @param _from address of token to swap - /// @param _amount of USDC to receive in return + /// @param _amount 6 decimal amount of USDC to receive in return /// @param _tolerance or tolerable amount of token to spend /// @param _receiver address to receive USDC /// @return deducted amount of incoming token; i.e., amount spent @@ -600,7 +603,8 @@ contract Zap is Errors { /// @dev caller must grant token allowance to this contract /// @param _from address of token to swap /// @param _amount of token to swap - /// @param _tolerance or tolerable amount of USDC to receive + /// @param _tolerance tolerable amount of USDC to receive specified with 6 + /// decimals /// @param _receiver address to receive USDC /// @return received amount of USDC function swapWith( From 71a08ee54770595a4f7cf25baf01b0395d902a53 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 27 Sep 2024 10:00:16 -0400 Subject: [PATCH 053/129] L-1 --- src/Zap.sol | 14 +++++++--- src/interfaces/ISynthetix.sol | 50 ++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 1cf0965..0200350 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -441,16 +441,22 @@ contract Zap is Errors { // determine amount of synthetix perp position collateral // i.e., # of sETH, # of sUSDC, # of sUSDe, # of stBTC, etc. - uint256 collateralAmount = IPerpsMarket(PERPS_MARKET) - .getCollateralAmount(_accountId, _collateralId); + uint256 withdrawableMargin = IPerpsMarket(PERPS_MARKET) + .getWithdrawableMargin(_accountId, _collateralId); + + // TODO, should we subtract (or do something else) the fees from the + // OrderFeesData return values? + (uint256 withdrawableAmount,) = ISpotMarket(SPOT_MARKET).quoteBuyExactIn( + SUSDC_SPOT_ID, withdrawableMargin, ISpotMarket.Tolerance.STRICT + ); // withdraw synthetix perp position collateral to this contract - _withdraw(_collateralId, collateralAmount, _accountId); + _withdraw(_collateralId, withdrawableAmount, _accountId); // unwrap synthetix perp position collateral; // i.e., sETH -> WETH, sUSDC -> USDC, etc. uint256 unwrapped = - _unwrap(_collateralId, collateralAmount, _swapTolerance); + _unwrap(_collateralId, withdrawableAmount, _swapTolerance); // establish unwrapped collateral address collateral = ISpotMarket(SPOT_MARKET).getSynth(_collateralId); diff --git a/src/interfaces/ISynthetix.sol b/src/interfaces/ISynthetix.sol index f55310e..7fd29f1 100644 --- a/src/interfaces/ISynthetix.sol +++ b/src/interfaces/ISynthetix.sol @@ -39,6 +39,18 @@ interface ISpotMarket { int256 wrapperFees; } + struct OrderFeesData { + uint256 fixedFees; + uint256 utilizationFees; + int256 skewFees; + int256 wrapperFees; + } + + enum Tolerance { + DEFAULT, + STRICT + } + /// @notice Wraps the specified amount and returns similar value of synth /// minus the fees. /// @dev Fees are collected from the user by way of the contract returning @@ -130,6 +142,25 @@ interface ISpotMarket { external returns (uint256 usdAmountReceived, Data memory fees); + /** + * @notice quote for buyExactIn. same parameters and return values as + * buyExactIn + * @param synthMarketId market id value + * @param usdAmount amount of USD to use for the trade + * @param stalenessTolerance this enum determines what staleness + * tolerance to use + * @return synthAmount return amount of synth given the USD amount - fees + * @return fees breakdown of all the quoted fees for the buy txn + */ + function quoteBuyExactIn( + uint128 synthMarketId, + uint256 usdAmount, + Tolerance stalenessTolerance + ) + external + view + returns (uint256 synthAmount, OrderFeesData memory fees); + } interface IERC7412 { @@ -223,16 +254,17 @@ interface IPerpsMarket { view returns (uint256 accountDebt); - /** - * @notice Gets the account's collateral value for a specific collateral. - * @param accountId Id of the account. - * @param collateralId Id of the synth market used as collateral. Synth - * market id, 0 for snxUSD. - * @return collateralValue collateral value of the account. - */ - function getCollateralAmount( + /// @notice Returns the withdrawable margin given the discounted margin for + /// `accountId` accounting for any open position. Callers can invoke + /// `getMarginDigest` to retrieve the available margin (i.e. discounted + /// margin when there is a position open or marginUsd when there isn't). + /// @param accountId Account of the margin account to query against + /// @param marketId Market of the margin account to query against + /// @return getWithdrawableMargin Amount of margin in USD that can be + /// withdrawn + function getWithdrawableMargin( uint128 accountId, - uint128 collateralId + uint128 marketId ) external view From 7e4e0e5ee3f211d653d76f385714fd832a577809 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 15:42:19 -0400 Subject: [PATCH 054/129] =?UTF-8?q?=F0=9F=91=B7=20add=20isAuthorized=20mod?= =?UTF-8?q?ifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 3f0c47f..fa3a0af 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -77,6 +77,20 @@ contract Zap is Enums, Errors { FEE_TIER = 3000; } + /*////////////////////////////////////////////////////////////// + MODIFIERS + //////////////////////////////////////////////////////////////*/ + + /// @notice validate caller is authorized to modify synthetix perp position + /// @param _accountId synthetix perp market account id + modifier isAuthorized(uint128 _accountId) { + bool authorized = IPerpsMarket(PERPS_MARKET).isAuthorized( + _accountId, MODIFY_PERMISSION, msg.sender + ); + require(authorized, NotPermitted()); + _; + } + /*////////////////////////////////////////////////////////////// ZAP //////////////////////////////////////////////////////////////*/ @@ -359,18 +373,14 @@ contract Zap is Enums, Errors { address _receiver ) external + isAuthorized(_accountId) { if (reentrancyGuard != MultiLevelReentrancyGuard.Unset) { revert ReentrancyGuardReentrantCall(); } else { reentrancyGuard = MultiLevelReentrancyGuard.Level1; } - - IPerpsMarket market = IPerpsMarket(PERPS_MARKET); - if (!market.hasPermission(_accountId, MODIFY_PERMISSION, msg.sender)) { - revert Errors.NotPermitted(); - } - + bytes memory params = abi.encode( _accountId, _collateralId, _zapTolerance, _swapTolerance, _receiver ); @@ -410,7 +420,7 @@ contract Zap is Enums, Errors { if (reentrancyGuard != MultiLevelReentrancyGuard.Level1) { revert ReentrancyGuardReentrantCall(); } else { - reentrancyGuard = MultiLevelReentrancyGuard.Level2; + reentrancyGuard = MultiLevelReentrancyGuard.Level2; } if (msg.sender != AAVE) { @@ -544,6 +554,7 @@ contract Zap is Enums, Errors { address _receiver ) external + isAuthorized(_accountId) { _withdraw(_synthId, _amount, _accountId); address synth = _synthId == USDX_ID From fdf33e1740cb72a6149b5c913bd750cb955323a1 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 15:59:50 -0400 Subject: [PATCH 055/129] =?UTF-8?q?=F0=9F=91=B7=20add=20pull=20and=20push?= =?UTF-8?q?=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Errors.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Errors.sol b/src/Errors.sol index 7848b52..d65ca73 100644 --- a/src/Errors.sol +++ b/src/Errors.sol @@ -31,4 +31,12 @@ contract Errors { /// @notice Unauthorized reentrant call. error ReentrancyGuardReentrantCall(); + /// @notice thrown when a pull operation fails + /// @param reason data for the failure + error PullFailed(bytes reason); + + /// @notice thrown when a push operation fails + /// @param reason data for the failure + error PushFailed(bytes reason); + } From d40af9fa5c4b79bdec5e0253769278fc72c7c212 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 16:00:05 -0400 Subject: [PATCH 056/129] =?UTF-8?q?=F0=9F=91=B7=20roll=20back=20interface?= =?UTF-8?q?=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/IERC20.sol | 135 +++++++++----------------------------- 1 file changed, 32 insertions(+), 103 deletions(-) diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 78fec06..cc89afe 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -1,118 +1,47 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) +pragma solidity 0.8.27; -pragma solidity ^0.8.20; - -/** - * @dev Interface of the ERC-20 standard as defined in the ERC. - * @author OpenZeppelin - */ +/// @title Reduced Interface of the ERC20 standard as defined in the EIP +/// @author OpenZeppelin interface IERC20 { - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval( - address indexed owner, address indexed spender, uint256 value - ); - - /** - * @dev Returns the value of tokens in existence. - */ - function totalSupply() external view returns (uint256); + /// @dev Returns the number of decimals used to get its user representation. + /// For example, if `decimals` equals `2`, a balance of `505` tokens should + /// be displayed to a user as `5.05` (`505 / 10 ** 2`). + function decimals() external view returns (uint8); - /** - * @dev Returns the value of tokens owned by `account`. - */ + /// @dev Returns the amount of tokens owned by `account`. function balanceOf(address account) external view returns (uint256); - /** - * @dev Moves a `value` amount of tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address to, uint256 value) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance( - address owner, - address spender - ) - external - view - returns (uint256); - - /** - * @dev Sets a `value` amount of tokens as the allowance of `spender` over - * the - * caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the - * risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 value) external returns (bool); - - /** - * @dev Moves a `value` amount of tokens from `from` to `to` using the - * allowance mechanism. `value` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ + /// @dev Moves `amount` tokens from the caller's account to `to` + /// @param to The address of the recipient + /// @param amount The amount of tokens to transfer + /// @return a boolean value indicating whether the operation succeeded + /// Emits a {Transfer} event + function transfer(address to, uint256 amount) external returns (bool); + + /// @dev Sets `amount` as the allowance of `spender` over the caller's + /// tokens + /// @param spender The address of the account to grant the allowance + /// @param amount The amount of tokens to allow + /// @return a boolean value indicating whether the operation succeeded + /// Emits an {Approval} event. + function approve(address spender, uint256 amount) external returns (bool); + + /// @dev Moves `amount` tokens from `from` to `to` using the + /// allowance mechanism. `amount` is then deducted from the caller's + /// allowance + /// @param from The address of the sender + /// @param to The address of the recipient + /// @param amount The amount of tokens to transfer + /// @return a boolean value indicating whether the operation succeeded. + /// Emits a {Transfer} event function transferFrom( address from, address to, - uint256 value + uint256 amount ) external returns (bool); - /** - * @dev Transfer `value` amount of `token` from the calling contract to - * `to`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeTransfer(IERC20 token, address to, uint256 value) external; - /** - * @dev Transfer `value` amount of `token` from `from` to `to`, spending the - * approval given by `from` to the - * calling contract. If `token` returns no value, non-reverting calls are - * assumed to be successful. - */ - function safeTransferFrom( - IERC20 token, - address from, - address to, - uint256 value - ) - external; - } From 0045ad86cba692ad6ccc1c9e8e42e6dc88f2c060 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 16:00:27 -0400 Subject: [PATCH 057/129] =?UTF-8?q?=F0=9F=91=B7=20wrap=20token=20transfers?= =?UTF-8?q?=20in=20try-catch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index fa3a0af..5cea22b 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -706,25 +706,35 @@ contract Zap is Enums, Errors { /// @param _token address of token to pull /// @param _from address of sender /// @param _amount amount of token to pull - /// @return bool representing successful execution + /// @return success boolean representing execution success function _pull( address _token, address _from, uint256 _amount ) internal - returns (bool) + returns (bool success) { IERC20 token = IERC20(_token); - token.safeTransferFrom(token, _from, address(this), _amount); - return true; + + try token.transferFrom(_from, address(this), _amount) returns ( + bool result + ) { + success = result; + require( + success, + PullFailed(abi.encodePacked(address(token), _from, _amount)) + ); + } catch Error(string memory reason) { + revert PullFailed(bytes(reason)); + } } /// @dev push tokens to a receiver /// @param _token address of token to push /// @param _receiver address of receiver /// @param _amount amount of token to push - /// @return bool representing successful execution + /// @return success boolean representing execution success function _push( address _token, address _receiver, @@ -734,8 +744,16 @@ contract Zap is Enums, Errors { returns (bool success) { IERC20 token = IERC20(_token); - token.safeTransfer(token, _receiver, _amount); - return true; + + try token.transfer(_receiver, _amount) returns (bool result) { + success = result; + require( + success, + PushFailed(abi.encodePacked(address(token), _receiver, _amount)) + ); + } catch Error(string memory reason) { + revert PushFailed(bytes(reason)); + } } } From 1cab309228681dca5b99d64355d95ebd460f4fdf Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 16:08:04 -0400 Subject: [PATCH 058/129] =?UTF-8?q?=F0=9F=91=B7=20add=20OnlyAave=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Errors.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Errors.sol b/src/Errors.sol index d65ca73..989eef4 100644 --- a/src/Errors.sol +++ b/src/Errors.sol @@ -39,4 +39,8 @@ contract Errors { /// @param reason data for the failure error PushFailed(bytes reason); + /// @notice thrown when caller is not Aave pool address + /// @param caller address of the msg.sender + error OnlyAave(address caller); + } From 8c81ef3213f5c2261273ded7d7ce55536c799501 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 16:08:29 -0400 Subject: [PATCH 059/129] =?UTF-8?q?=F0=9F=91=B7=20refactor=20caller=20chec?= =?UTF-8?q?k=20to=20throw=20with=20OnlyAave=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 5cea22b..389059f 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -417,16 +417,14 @@ contract Zap is Enums, Errors { external returns (bool) { + require(msg.sender == AAVE, OnlyAave(msg.sender)); + if (reentrancyGuard != MultiLevelReentrancyGuard.Level1) { revert ReentrancyGuardReentrantCall(); } else { reentrancyGuard = MultiLevelReentrancyGuard.Level2; } - if (msg.sender != AAVE) { - revert Errors.NotPermitted(); - } - ( uint128 _accountId, uint128 _collateralId, From 0d0779249fc77e636da56dce98fd10d14b7464d0 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 16:55:34 -0400 Subject: [PATCH 060/129] =?UTF-8?q?=F0=9F=91=B7=20introduce=20modifiers=20?= =?UTF-8?q?to=20prevent=20reentrancy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Enums.sol | 15 -------- src/Zap.sol | 79 +++++++++++++++++++------------------- src/{ => utils}/Errors.sol | 0 src/utils/Reentrancy.sol | 35 +++++++++++++++++ 4 files changed, 74 insertions(+), 55 deletions(-) delete mode 100644 src/Enums.sol rename src/{ => utils}/Errors.sol (100%) create mode 100644 src/utils/Reentrancy.sol diff --git a/src/Enums.sol b/src/Enums.sol deleted file mode 100644 index 9d14ebe..0000000 --- a/src/Enums.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -/// @title zap enums -/// @author @moss-eth -contract Enums { - - /// @notice multi-level reentrancy guard to protect callback function - enum MultiLevelReentrancyGuard { - Unset, - Level1, - Level2 - } - -} diff --git a/src/Zap.sol b/src/Zap.sol index 389059f..3213935 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {Enums} from "./Enums.sol"; -import {Errors} from "./Errors.sol"; import {IPool} from "./interfaces/IAave.sol"; import {IERC20} from "./interfaces/IERC20.sol"; import {ICore, IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; import {IUniswap} from "./interfaces/IUniswap.sol"; +import {Errors} from "./utils/Errors.sol"; +import {Reentrancy} from "./utils/Reentrancy.sol"; /// @title Zap /// @custom:synthetix Zap USDC into and out of USDx @@ -18,7 +18,7 @@ import {IUniswap} from "./interfaces/IUniswap.sol"; /// @author @barrasso /// @author @Flocqst /// @author @moss-eth -contract Zap is Enums, Errors { +contract Zap is Reentrancy, Errors { /// @custom:synthetix address public immutable USDC; @@ -41,9 +41,6 @@ contract Zap is Enums, Errors { address public immutable UNISWAP; uint24 public immutable FEE_TIER; - /// @dev set to `Unset` by default - MultiLevelReentrancyGuard reentrancyGuard; - constructor( address _usdc, address _usdx, @@ -91,6 +88,12 @@ contract Zap is Enums, Errors { _; } + /// @notice validate caller is Aave lending pool + modifier onlyAave() { + require(msg.sender == AAVE, OnlyAave(msg.sender)); + _; + } + /*////////////////////////////////////////////////////////////// ZAP //////////////////////////////////////////////////////////////*/ @@ -374,12 +377,10 @@ contract Zap is Enums, Errors { ) external isAuthorized(_accountId) + requireStage(Stage.UNSET) + onlyAave { - if (reentrancyGuard != MultiLevelReentrancyGuard.Unset) { - revert ReentrancyGuardReentrantCall(); - } else { - reentrancyGuard = MultiLevelReentrancyGuard.Level1; - } + stage = Stage.LEVEL1; bytes memory params = abi.encode( _accountId, _collateralId, _zapTolerance, _swapTolerance, _receiver @@ -399,6 +400,8 @@ contract Zap is Enums, Errors { params: params, referralCode: REFERRAL_CODE }); + + stage = Stage.UNSET; } /// @notice flashloan callback function @@ -415,40 +418,35 @@ contract Zap is Enums, Errors { bytes calldata _params ) external + requireStage(Stage.LEVEL1) returns (bool) { - require(msg.sender == AAVE, OnlyAave(msg.sender)); - - if (reentrancyGuard != MultiLevelReentrancyGuard.Level1) { - revert ReentrancyGuardReentrantCall(); - } else { - reentrancyGuard = MultiLevelReentrancyGuard.Level2; - } - - ( - uint128 _accountId, - uint128 _collateralId, - uint256 _zapTolerance, - uint256 _swapTolerance, - address _receiver - ) = abi.decode(_params, (uint128, uint128, uint256, uint256, address)); - - (uint256 unwound, address collateral) = _unwind( - _flashloan, - _premium, - _accountId, - _collateralId, - _zapTolerance, - _swapTolerance - ); - - _flashloan += _premium; + stage = Stage.LEVEL2; + + { + ( + uint128 _accountId, + uint128 _collateralId, + uint256 _zapTolerance, + uint256 _swapTolerance, + address _receiver + ) = abi.decode( + _params, (uint128, uint128, uint256, uint256, address) + ); - IERC20(USDC).approve(AAVE, _flashloan); + (uint256 unwound, address collateral) = _unwind( + _flashloan, + _premium, + _accountId, + _collateralId, + _zapTolerance, + _swapTolerance + ); - reentrancyGuard = MultiLevelReentrancyGuard.Unset; + _push(collateral, _receiver, unwound); + } - return _push(collateral, _receiver, unwound); + return IERC20(USDC).approve(AAVE, _flashloan + _premium); } /// @dev unwinds synthetix perp position collateral @@ -469,6 +467,7 @@ contract Zap is Enums, Errors { uint256 _swapTolerance ) internal + requireStage(Stage.LEVEL2) returns (uint256 unwound, address collateral) { // zap USDC from flashloan into USDx diff --git a/src/Errors.sol b/src/utils/Errors.sol similarity index 100% rename from src/Errors.sol rename to src/utils/Errors.sol diff --git a/src/utils/Reentrancy.sol b/src/utils/Reentrancy.sol new file mode 100644 index 0000000..44be590 --- /dev/null +++ b/src/utils/Reentrancy.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +/// @title multi-level reentrancy guard +/// @author @moss-eth +/// @author @jaredborders +contract Reentrancy { + + /// @notice enumerated stages of execution + /// @dev each stage denotes a different level of protection + enum Stage { + UNSET, + LEVEL1, + LEVEL2 + } + + /// @notice current stage of execution + Stage internal stage; + + /// @notice thrown when stage of execution is not expected + /// @param actual current stage of execution + /// @param expected expected stage of execution + error ReentrancyDetected(Stage actual, Stage expected); + + /// @notice validate current stage of execution is as expected + /// @param expected stage of execution + modifier requireStage(Stage expected) { + require( + expected == stage, + ReentrancyDetected({actual: stage, expected: expected}) + ); + _; + } + +} From 517e0a2154eef446ffa58ad5657d32e933dadadc Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 20:34:47 -0400 Subject: [PATCH 061/129] =?UTF-8?q?=F0=9F=91=B7=20add=20=5FunwrapTolerance?= =?UTF-8?q?=20and=20=5FapproximateLoanNeeded()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 79 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 3213935..bc19862 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -20,8 +20,10 @@ import {Reentrancy} from "./utils/Reentrancy.sol"; /// @author @moss-eth contract Zap is Reentrancy, Errors { - /// @custom:synthetix + /// @custom:circle address public immutable USDC; + + /// @custom:synthetix address public immutable USDX; address public immutable SPOT_MARKET; address public immutable PERPS_MARKET; @@ -52,8 +54,10 @@ contract Zap is Reentrancy, Errors { address _aave, address _uniswap ) { - /// @custom:synthetix + /// @custom:circle USDC = _usdc; + + /// @custom:synthetix USDX = _usdx; SPOT_MARKET = _spotMarket; PERPS_MARKET = _perpsMarket; @@ -366,12 +370,14 @@ contract Zap is Reentrancy, Errors { /// @param _accountId synthetix perp market account id /// @param _collateralId synthetix market id of collateral /// @param _zapTolerance acceptable slippage for zapping + /// @param _unwrapTolerance acceptable slippage for unwrapping /// @param _swapTolerance acceptable slippage for swapping /// @param _receiver address to receive unwound collateral function unwind( uint128 _accountId, uint128 _collateralId, uint256 _zapTolerance, + uint256 _unwrapTolerance, uint256 _swapTolerance, address _receiver ) @@ -383,20 +389,21 @@ contract Zap is Reentrancy, Errors { stage = Stage.LEVEL1; bytes memory params = abi.encode( - _accountId, _collateralId, _zapTolerance, _swapTolerance, _receiver + _accountId, + _collateralId, + _zapTolerance, + _unwrapTolerance, + _swapTolerance, + _receiver ); - // determine amount of synthetix perp position debt to unwind; - // debt is denominated in USD with 18 decimals - // the Aave USDC pool uses USDC token amounts denominated with 6 - // decimals - uint256 debt = IPerpsMarket(PERPS_MARKET).debt(_accountId); - uint256 debt6Decimals = debt / (10 ** (18 - 6)); + // determine amount of synthetix perp position debt to unwind + uint256 debt = _approximateLoanNeeded(_accountId); IPool(AAVE).flashLoanSimple({ receiverAddress: address(this), asset: USDC, - amount: debt6Decimals, + amount: debt, params: params, referralCode: REFERRAL_CODE }); @@ -428,10 +435,11 @@ contract Zap is Reentrancy, Errors { uint128 _accountId, uint128 _collateralId, uint256 _zapTolerance, + uint256 _unwrapTolerance, uint256 _swapTolerance, address _receiver ) = abi.decode( - _params, (uint128, uint128, uint256, uint256, address) + _params, (uint128, uint128, uint256, uint256, uint256, address) ); (uint256 unwound, address collateral) = _unwind( @@ -440,6 +448,7 @@ contract Zap is Reentrancy, Errors { _accountId, _collateralId, _zapTolerance, + _unwrapTolerance, _swapTolerance ); @@ -455,6 +464,7 @@ contract Zap is Reentrancy, Errors { /// @param _accountId synthetix perp market account id /// @param _collateralId synthetix market id of collateral /// @param _zapTolerance acceptable slippage for zapping + /// @param _unwrapTolerance acceptable slippage for unwrapping /// @param _swapTolerance acceptable slippage for swapping /// @return unwound amount of collateral /// @return collateral address @@ -464,6 +474,7 @@ contract Zap is Reentrancy, Errors { uint128 _accountId, uint128 _collateralId, uint256 _zapTolerance, + uint256 _unwrapTolerance, uint256 _swapTolerance ) internal @@ -479,27 +490,28 @@ contract Zap is Reentrancy, Errors { // determine amount of synthetix perp position collateral // i.e., # of sETH, # of sUSDC, # of sUSDe, # of stBTC, etc. - uint256 withdrawableMargin = IPerpsMarket(PERPS_MARKET) - .getWithdrawableMargin(_accountId, _collateralId); + unwound = IPerpsMarket(PERPS_MARKET).getWithdrawableMargin( + _accountId, _collateralId + ); // TODO, should we subtract (or do something else) the fees from the // OrderFeesData return values? - (uint256 withdrawableAmount,) = ISpotMarket(SPOT_MARKET).quoteBuyExactIn( - SUSDC_SPOT_ID, withdrawableMargin, ISpotMarket.Tolerance.STRICT + (unwound,) = ISpotMarket(SPOT_MARKET).quoteBuyExactIn( + SUSDC_SPOT_ID, unwound, ISpotMarket.Tolerance.STRICT ); // withdraw synthetix perp position collateral to this contract - _withdraw(_collateralId, withdrawableAmount, _accountId); + _withdraw(_collateralId, unwound, _accountId); // unwrap synthetix perp position collateral; // i.e., sETH -> WETH, sUSDC -> USDC, etc. - uint256 unwrapped = - _unwrap(_collateralId, withdrawableAmount, _swapTolerance); + unwound = _unwrap(_collateralId, unwound, _unwrapTolerance); // establish unwrapped collateral address collateral = ISpotMarket(SPOT_MARKET).getSynth(_collateralId); - // establish total debt now owed to Aave + // establish total debt now owed to Aave; + // i.e., denominated in USDC _flashloan += _premium; // swap as necessary to repay Aave flashloan; @@ -507,7 +519,32 @@ contract Zap is Reentrancy, Errors { uint256 deducted = _swapFor(collateral, _flashloan, _swapTolerance); // establish amount of unwound collateral after deduction - unwound = unwrapped - deducted; + unwound -= deducted; + } + + /// @notice approximate USDC needed to unwind synthetix perp position + /// @param _accountId synthetix perp market account id + /// @return amount of USDC needed + function _approximateLoanNeeded(uint128 _accountId) + internal + view + returns (uint256 amount) + { + // determine amount of debt associated with synthetix perp position + amount = IPerpsMarket(PERPS_MARKET).debt(_accountId); + + uint256 usdxDecimals = IERC20(USDX).decimals(); + uint256 usdcDecimals = IERC20(USDC).decimals(); + + /// @custom:synthetix debt is denominated in USDx + /// @custom:aave debt is denominated in USDC + /// @dev scale loan samount accordingly + amount /= 10 ** (usdxDecimals - usdcDecimals); + + /// @dev barring exceptional circumstances, + /// a 1 USD buffer is sufficient to circumvent + /// precision loss + amount += 10 ** usdcDecimals; } /*////////////////////////////////////////////////////////////// @@ -587,7 +624,7 @@ contract Zap is Reentrancy, Errors { /// @dev caller must grant token allowance to this contract /// @dev any excess token not spent will be returned to the caller /// @param _from address of token to swap - /// @param _amount 6 decimal amount of USDC to receive in return + /// @param _amount amount of USDC to receive in return /// @param _tolerance or tolerable amount of token to spend /// @param _receiver address to receive USDC /// @return deducted amount of incoming token; i.e., amount spent From b572d09a837698a1026e7ab50f1ce22a319d7a3f Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 20:36:48 -0400 Subject: [PATCH 062/129] =?UTF-8?q?=F0=9F=91=B7=20approve=20the=20correct?= =?UTF-8?q?=20address=20before=20burning=20debt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Zap.sol b/src/Zap.sol index bc19862..680881f 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -567,7 +567,7 @@ contract Zap is Reentrancy, Errors { /// @dev allowance is assumed /// @dev following execution, this contract will hold any excess USDx function _burn(uint256 _amount, uint128 _accountId) internal { - IERC20(USDX).approve(CORE, _amount); + IERC20(USDX).approve(PERPS_MARKET, _amount); IPerpsMarket(PERPS_MARKET).payDebt(_accountId, _amount); } From 0ab23e38b4df9bafb77119d96e554bbf2cbd7770 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 27 Sep 2024 20:53:28 -0400 Subject: [PATCH 063/129] fmt --- src/Zap.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 3f0c47f..61d420a 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -365,12 +365,12 @@ contract Zap is Enums, Errors { } else { reentrancyGuard = MultiLevelReentrancyGuard.Level1; } - + IPerpsMarket market = IPerpsMarket(PERPS_MARKET); if (!market.hasPermission(_accountId, MODIFY_PERMISSION, msg.sender)) { revert Errors.NotPermitted(); } - + bytes memory params = abi.encode( _accountId, _collateralId, _zapTolerance, _swapTolerance, _receiver ); @@ -410,7 +410,7 @@ contract Zap is Enums, Errors { if (reentrancyGuard != MultiLevelReentrancyGuard.Level1) { revert ReentrancyGuardReentrantCall(); } else { - reentrancyGuard = MultiLevelReentrancyGuard.Level2; + reentrancyGuard = MultiLevelReentrancyGuard.Level2; } if (msg.sender != AAVE) { From d5ce24560b66c99a818621c2b3fc346587876096 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 21:28:37 -0400 Subject: [PATCH 064/129] =?UTF-8?q?=F0=9F=91=B7=20allow=20caller=20to=20sp?= =?UTF-8?q?ecify=20collateral=20amount=20to=20unwind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 97 ++++++++++++++++++++++------------------------------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 680881f..32e4ca3 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -369,6 +369,7 @@ contract Zap is Reentrancy, Errors { /// @custom:synthetix RBAC permission required: "PERPS_MODIFY_COLLATERAL" /// @param _accountId synthetix perp market account id /// @param _collateralId synthetix market id of collateral + /// @param _collateralAmount amount of collateral to unwind /// @param _zapTolerance acceptable slippage for zapping /// @param _unwrapTolerance acceptable slippage for unwrapping /// @param _swapTolerance acceptable slippage for swapping @@ -376,6 +377,7 @@ contract Zap is Reentrancy, Errors { function unwind( uint128 _accountId, uint128 _collateralId, + uint256 _collateralAmount, uint256 _zapTolerance, uint256 _unwrapTolerance, uint256 _swapTolerance, @@ -391,6 +393,7 @@ contract Zap is Reentrancy, Errors { bytes memory params = abi.encode( _accountId, _collateralId, + _collateralAmount, _zapTolerance, _unwrapTolerance, _swapTolerance, @@ -416,6 +419,7 @@ contract Zap is Reentrancy, Errors { /// @custom:caution calling this function directly is not recommended /// @param _flashloan amount of USDC flashloaned from Aave /// @param _premium amount of USDC premium owed to Aave + /// @param _params encoded parameters for unwinding synthetix perp position /// @return bool representing successful execution function executeOperation( address, @@ -430,30 +434,15 @@ contract Zap is Reentrancy, Errors { { stage = Stage.LEVEL2; - { - ( - uint128 _accountId, - uint128 _collateralId, - uint256 _zapTolerance, - uint256 _unwrapTolerance, - uint256 _swapTolerance, - address _receiver - ) = abi.decode( - _params, (uint128, uint128, uint256, uint256, uint256, address) - ); + (,,,,,, address _receiver) = abi.decode( + _params, + (uint128, uint128, uint256, uint256, uint256, uint256, address) + ); - (uint256 unwound, address collateral) = _unwind( - _flashloan, - _premium, - _accountId, - _collateralId, - _zapTolerance, - _unwrapTolerance, - _swapTolerance - ); + (uint256 unwound, address collateral) = + _unwind(_flashloan, _premium, _params); - _push(collateral, _receiver, unwound); - } + _push(collateral, _receiver, unwound); return IERC20(USDC).approve(AAVE, _flashloan + _premium); } @@ -461,26 +450,30 @@ contract Zap is Reentrancy, Errors { /// @dev unwinds synthetix perp position collateral /// @param _flashloan amount of USDC flashloaned from Aave /// @param _premium amount of USDC premium owed to Aave - /// @param _accountId synthetix perp market account id - /// @param _collateralId synthetix market id of collateral - /// @param _zapTolerance acceptable slippage for zapping - /// @param _unwrapTolerance acceptable slippage for unwrapping - /// @param _swapTolerance acceptable slippage for swapping + /// @param _params encoded parameters for unwinding synthetix perp position /// @return unwound amount of collateral /// @return collateral address function _unwind( uint256 _flashloan, uint256 _premium, - uint128 _accountId, - uint128 _collateralId, - uint256 _zapTolerance, - uint256 _unwrapTolerance, - uint256 _swapTolerance + bytes calldata _params ) internal requireStage(Stage.LEVEL2) returns (uint256 unwound, address collateral) { + ( + uint128 _accountId, + uint128 _collateralId, + uint256 _collateralAmount, + uint256 _zapTolerance, + uint256 _unwrapTolerance, + uint256 _swapTolerance, + ) = abi.decode( + _params, + (uint128, uint128, uint256, uint256, uint256, uint256, address) + ); + // zap USDC from flashloan into USDx uint256 usdxAmount = _zapIn(_flashloan, _zapTolerance); @@ -488,38 +481,30 @@ contract Zap is Reentrancy, Errors { // debt is denominated in USD and thus repaid with USDx _burn(usdxAmount, _accountId); - // determine amount of synthetix perp position collateral - // i.e., # of sETH, # of sUSDC, # of sUSDe, # of stBTC, etc. - unwound = IPerpsMarket(PERPS_MARKET).getWithdrawableMargin( - _accountId, _collateralId - ); - - // TODO, should we subtract (or do something else) the fees from the - // OrderFeesData return values? - (unwound,) = ISpotMarket(SPOT_MARKET).quoteBuyExactIn( - SUSDC_SPOT_ID, unwound, ISpotMarket.Tolerance.STRICT - ); + // withdraw synthetix perp position collateral to this contract; + // i.e., # of sETH, # of sUSDe, # of sUSDC (...) + _withdraw(_collateralId, _collateralAmount, _accountId); - // withdraw synthetix perp position collateral to this contract - _withdraw(_collateralId, unwound, _accountId); - - // unwrap synthetix perp position collateral; - // i.e., sETH -> WETH, sUSDC -> USDC, etc. - unwound = _unwrap(_collateralId, unwound, _unwrapTolerance); + // unwrap withdrawn synthetix perp position collateral; + // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) + unwound = _unwrap(_collateralId, _collateralAmount, _unwrapTolerance); // establish unwrapped collateral address + // i.e., WETH, USDe, USDC (...) collateral = ISpotMarket(SPOT_MARKET).getSynth(_collateralId); // establish total debt now owed to Aave; - // i.e., denominated in USDC + // i.e., # of USDC _flashloan += _premium; - // swap as necessary to repay Aave flashloan; - // only as much as necessary to repay the flashloan - uint256 deducted = _swapFor(collateral, _flashloan, _swapTolerance); - - // establish amount of unwound collateral after deduction - unwound -= deducted; + // swap as much (or little) as necessary to repay Aave flashloan; + // i.e., WETH -(swap)-> USDC -(repay)-> Aave + // i.e., USDe -(swap)-> USDC -(repay)-> Aave + // i.e., USDC -(repay)-> Aave + // whatever collateral amount is remaining is returned to the caller + unwound -= collateral == USDC + ? _flashloan + : _swapFor(collateral, _flashloan, _swapTolerance); } /// @notice approximate USDC needed to unwind synthetix perp position From edcb48b7940da5dfca6aff79d6e34e2b7cb673e8 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 22:32:33 -0400 Subject: [PATCH 065/129] =?UTF-8?q?=F0=9F=91=B7=20move=20only=20aave=20mod?= =?UTF-8?q?ifier=20to=20the=20correct=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 32e4ca3..e68644a 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -386,7 +386,6 @@ contract Zap is Reentrancy, Errors { external isAuthorized(_accountId) requireStage(Stage.UNSET) - onlyAave { stage = Stage.LEVEL1; @@ -415,7 +414,7 @@ contract Zap is Reentrancy, Errors { } /// @notice flashloan callback function - /// @dev caller is expected to be the Aave lending pool + /// @dev caller must be the Aave lending pool /// @custom:caution calling this function directly is not recommended /// @param _flashloan amount of USDC flashloaned from Aave /// @param _premium amount of USDC premium owed to Aave @@ -429,6 +428,7 @@ contract Zap is Reentrancy, Errors { bytes calldata _params ) external + onlyAave requireStage(Stage.LEVEL1) returns (bool) { From 4edcd523421ff6ac17eb6355843f52904861fe9e Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 22:32:51 -0400 Subject: [PATCH 066/129] =?UTF-8?q?=E2=9B=B3=EF=B8=8F=20optimize=20bytecod?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/Reentrancy.sol | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/utils/Reentrancy.sol b/src/utils/Reentrancy.sol index 44be590..504e728 100644 --- a/src/utils/Reentrancy.sol +++ b/src/utils/Reentrancy.sol @@ -25,11 +25,15 @@ contract Reentrancy { /// @notice validate current stage of execution is as expected /// @param expected stage of execution modifier requireStage(Stage expected) { + _requireStage(expected); + _; + } + + function _requireStage(Stage _expected) internal view { require( - expected == stage, - ReentrancyDetected({actual: stage, expected: expected}) + _expected == stage, + ReentrancyDetected({actual: stage, expected: _expected}) ); - _; } } From 5e812c227ea743902afa4869d00b94fbceea9dc1 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 22:33:03 -0400 Subject: [PATCH 067/129] =?UTF-8?q?=E2=9C=85=20add=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Aave.t.sol | 53 +++++++++++++++++++++++ test/Burn.t.sol | 2 +- test/Flash.t.sol | 64 ---------------------------- test/Reentrancy.t.sol | 60 ++++++++++++++++++++++++++ test/Swap.for.t.sol | 24 +++++++++++ test/{Swap.t.sol => Swap.with.t.sol} | 8 +--- test/Unwind.t.sol | 37 ++++++++++++++++ test/Withdraw.t.sol | 15 ++++++- test/utils/Bootstrap.sol | 2 + 9 files changed, 192 insertions(+), 73 deletions(-) create mode 100644 test/Aave.t.sol delete mode 100644 test/Flash.t.sol create mode 100644 test/Reentrancy.t.sol create mode 100644 test/Swap.for.t.sol rename test/{Swap.t.sol => Swap.with.t.sol} (66%) create mode 100644 test/Unwind.t.sol diff --git a/test/Aave.t.sol b/test/Aave.t.sol new file mode 100644 index 0000000..c133a7d --- /dev/null +++ b/test/Aave.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + Errors, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract AaveTest is Bootstrap, Errors { + + function test_only_aave_arbitrum( + address caller, + address a, + uint256 b, + uint256 c, + bytes calldata d + ) + public + arbitrum + { + if (caller != zap.AAVE()) { + vm.prank(caller); + vm.expectRevert(abi.encodeWithSelector(OnlyAave.selector, caller)); + zap.executeOperation(a, b, c, a, d); + } + } + + function test_only_aave_base( + address caller, + address a, + uint256 b, + uint256 c, + bytes calldata d + ) + public + base + { + if (caller != zap.AAVE()) { + vm.prank(caller); + vm.expectRevert(abi.encodeWithSelector(OnlyAave.selector, caller)); + zap.executeOperation(a, b, c, a, d); + } + } + +} diff --git a/test/Burn.t.sol b/test/Burn.t.sol index 4b9f4eb..6b745c4 100644 --- a/test/Burn.t.sol +++ b/test/Burn.t.sol @@ -15,7 +15,7 @@ import { contract BurnTest is Bootstrap { - /// @custom:todo on arbitrum sepolia fork + /// @custom:todo function test_burn_arbitrum_sepolia(uint32 amount) public {} } diff --git a/test/Flash.t.sol b/test/Flash.t.sol deleted file mode 100644 index fe9b014..0000000 --- a/test/Flash.t.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import { - Bootstrap, - Constants, - ICore, - IERC20, - IPerpsMarket, - IPool, - ISpotMarket, - Test, - Zap -} from "./utils/Bootstrap.sol"; - -contract FlashTest is Bootstrap { - - /// @custom:todo on arbitrum sepolia fork - function test_flash_arbitrum_sepolia() public { - /** - * // 0. define bob; - * vm.startPrank(ARBITRUM_BOB); - * - * // 1. define amount; 1000 usdx == $1000 - * uint128 amount = 1000 ether; - * - * // 2. spin up bob with 1000 usdx - * _spin(ARBITRUM_BOB, usdx, amount, address(zap)); - * - * // 3. create perp market account - * uint128 accountId = perpsMarket.createAccount(); - * - * // 4. grant perp market usdx allowance - * usdx.approve(address(perpsMarket), type(uint256).max); - * - * // 5. grant zap permission to modify an account's collateral - * perpsMarket.grantPermission( - * accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) - * ); - * - * // 6. add 1000 usdx as collateral - * perpsMarket.modifyCollateral(accountId, 0, int128(amount)); - * - * // 7. determine debt - * uint256 accountDebt = perpsMarket.debt(accountId); - * - * // 8. request flash loan - * zap.requestFlashloan({ - * _usdcLoan: accountDebt, - * _collateralAmount: amount, - * _collateral: address(usdc), - * _accountId: accountId, - * _synthId: ARBITRUM_SUSDC_SPOT_MARKET_ID, - * _tolerance: DEFAULT_TOLERANCE, - * _swapTolerance: DEFAULT_TOLERANCE, - * receiver: ARBITRUM_BOB - * }); - * - * // 9. end bob prank - * vm.stopPrank(); - */ - } - -} diff --git a/test/Reentrancy.t.sol b/test/Reentrancy.t.sol new file mode 100644 index 0000000..62915cd --- /dev/null +++ b/test/Reentrancy.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Reentrancy, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract ReentrancyTest is Bootstrap, Reentrancy { + + function test_stage_enum_unset() public { + assertEq(uint256(Stage.UNSET), 0); + } + + function test_stage_enum_level1() public { + assertEq(uint256(Stage.LEVEL1), 1); + } + + function test_stage_enum_level2() public { + assertEq(uint256(Stage.LEVEL2), 2); + } + + function test_stage_default() public { + assertEq(uint256(stage), 0); + } + + function test_requireStage(uint8 actual) public { + vm.assume(actual < 3); + stage = Stage(actual); + if (actual == 0) { + _requireStage(Stage.UNSET); + } else if (actual == 1) { + _requireStage(Stage.LEVEL1); + } else if (actual == 2) { + _requireStage(Stage.LEVEL2); + } + } + + function test_requireStage_throws(uint8 actual, uint256 expected) public { + vm.assume(actual < 3 && expected < 3); + stage = Stage(actual); + if (expected != actual) { + vm.expectRevert( + abi.encodeWithSelector( + ReentrancyDetected.selector, stage, Stage(expected) + ) + ); + _requireStage(Stage(expected)); + } + } + +} diff --git a/test/Swap.for.t.sol b/test/Swap.for.t.sol new file mode 100644 index 0000000..85aa475 --- /dev/null +++ b/test/Swap.for.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract SwapForTest is Bootstrap { + + /// @custom:todo + function test_swap_for_base() public base {} + + /// @custom:todo + function test_swap_for_arbitrum() public arbitrum {} + +} diff --git a/test/Swap.t.sol b/test/Swap.with.t.sol similarity index 66% rename from test/Swap.t.sol rename to test/Swap.with.t.sol index 8e86cfc..363c6d6 100644 --- a/test/Swap.t.sol +++ b/test/Swap.with.t.sol @@ -13,17 +13,11 @@ import { Zap } from "./utils/Bootstrap.sol"; -contract SwapTest is Bootstrap { - - /// @custom:todo - function test_swap_for_base() public base {} +contract SwapWithTest is Bootstrap { /// @custom:todo function test_swap_with_base() public base {} - /// @custom:todo - function test_swap_for_arbitrum() public arbitrum {} - /// @custom:todo function test_swap_with_arbitrum() public arbitrum {} diff --git a/test/Unwind.t.sol b/test/Unwind.t.sol new file mode 100644 index 0000000..6efd69c --- /dev/null +++ b/test/Unwind.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + Errors, + ICore, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract UnwindTest is Bootstrap, Errors { + + function test_unwind_is_authorized_arbitrum() public arbitrum { + vm.prank(ACTOR); + vm.expectRevert(NotPermitted.selector); + zap.unwind(0, 0, 0, 0, 0, 0, address(0)); + } + + function test_unwind_is_authorized_base() public base { + vm.prank(ACTOR); + vm.expectRevert(NotPermitted.selector); + zap.unwind(0, 0, 0, 0, 0, 0, address(0)); + } + + /// @custom:todo + function test_unwind_arbitrum_sepolia() public {} + + /// @custom:todo + function test_unwind_arbitrum() public {} + +} diff --git a/test/Withdraw.t.sol b/test/Withdraw.t.sol index 9bd9570..1954285 100644 --- a/test/Withdraw.t.sol +++ b/test/Withdraw.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, + Errors, ICore, IERC20, IPerpsMarket, @@ -13,7 +14,19 @@ import { Zap } from "./utils/Bootstrap.sol"; -contract WithdrawTest is Bootstrap { +contract WithdrawTest is Bootstrap, Errors { + + function test_withdraw_is_authorized_arbitrum() public arbitrum { + vm.prank(ACTOR); + vm.expectRevert(NotPermitted.selector); + zap.withdraw(0, 0, 0, address(0)); + } + + function test_withdraw_is_authorized_base() public base { + vm.prank(ACTOR); + vm.expectRevert(NotPermitted.selector); + zap.withdraw(0, 0, 0, address(0)); + } function test_withdraw_base() public base { uint32 amount = 1_000_000_000; diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol index 35aa49a..d1f5c11 100644 --- a/test/utils/Bootstrap.sol +++ b/test/utils/Bootstrap.sol @@ -3,11 +3,13 @@ pragma solidity 0.8.27; import {Arbitrum, Base} from "../../script/utils/Parameters.sol"; import { + Errors, ICore, IERC20, IPerpsMarket, IPool, ISpotMarket, + Reentrancy, Zap } from "../../src/Zap.sol"; import {Constants} from "../utils/Constants.sol"; From eef82b2bac7ff2143d83d68c094f576702d2e577 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 22:37:23 -0400 Subject: [PATCH 068/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20npm=20scr?= =?UTF-8?q?ipts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 656dee9..973ec51 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,12 @@ "homepage": "https://github.com/JaredBorders/zap#readme", "scripts": { "compile": "forge build", - "test": "forge test --fork-url $(grep BASE_RPC_URL .env | cut -d '=' -f2) --etherscan-api-key $(grep BASESCAN_API_KEY .env | cut -d '=' -f2) --gas-report -vvv", + "test": "forge test --gas-report -vvv", "format": "forge fmt", - "coverage": "forge coverage --fork-url $(grep BASE_RPC_URL .env | cut -d '=' -f2)", - "coverage:generate-lcov": "forge coverage --fork-url $(grep BASE_RPC_URL .env | cut -d '=' -f2) --report lcov", + "coverage": "forge coverage", + "coverage:generate-lcov": "forge coverage --report lcov", "analysis:slither": "slither .", - "gas-snapshot": "forge snapshot --fork-url $(grep BASE_RPC_URL .env | cut -d '=' -f2)", + "gas-snapshot": "forge snapshot", "decode-custom-error": "npx @usecannon/cli decode synthetix-perps-market" }, "repository": { From f3fc6f42022f441188c8e2d58a02e6dd658e6794 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 27 Sep 2024 22:37:41 -0400 Subject: [PATCH 069/129] =?UTF-8?q?=F0=9F=93=B8=20update=20gas-snapshot/lc?= =?UTF-8?q?ov?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 33 ++++ lcov.info | 527 ++++++++++++++++++++++++++++---------------------- 2 files changed, 330 insertions(+), 230 deletions(-) create mode 100644 .gas-snapshot diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 0000000..94b5c4c --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,33 @@ +AaveTest:test_only_aave_arbitrum(address,address,uint256,uint256,bytes) (runs: 264, μ: 3569236, ~: 3569235) +AaveTest:test_only_aave_base(address,address,uint256,uint256,bytes) (runs: 264, μ: 3569279, ~: 3569278) +BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 264, μ: 434, ~: 434) +BuyTest:test_buy_arbitrum(uint32) (runs: 258, μ: 4124020, ~: 4124020) +BuyTest:test_buy_base(uint32) (runs: 258, μ: 4043670, ~: 4043670) +ReentrancyTest:test_requireStage(uint8) (runs: 259, μ: 15965, ~: 6343) +ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 16405, ~: 8875) +ReentrancyTest:test_stage_default() (gas: 2435) +ReentrancyTest:test_stage_enum_level1() (gas: 250) +ReentrancyTest:test_stage_enum_level2() (gas: 294) +ReentrancyTest:test_stage_enum_unset() (gas: 293) +SellTest:test_sell_arbitrum(uint32) (runs: 258, μ: 4293543, ~: 4293543) +SellTest:test_sell_base(uint32) (runs: 258, μ: 4195005, ~: 4195005) +SwapForTest:test_swap_for_arbitrum() (gas: 3562157) +SwapForTest:test_swap_for_base() (gas: 3562178) +SwapWithTest:test_swap_with_arbitrum() (gas: 3562178) +SwapWithTest:test_swap_with_base() (gas: 3562201) +UnwindTest:test_unwind_arbitrum() (gas: 167) +UnwindTest:test_unwind_arbitrum_sepolia() (gas: 211) +UnwindTest:test_unwind_is_authorized_arbitrum() (gas: 3586536) +UnwindTest:test_unwind_is_authorized_base() (gas: 3586580) +UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 258, μ: 4337781, ~: 4337781) +UnwrapTest:test_unwrap_base(uint32) (runs: 258, μ: 4229739, ~: 4229739) +WithdrawTest:test_withdraw_arbitrum() (gas: 4406465) +WithdrawTest:test_withdraw_base() (gas: 4229858) +WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 3585402) +WithdrawTest:test_withdraw_is_authorized_base() (gas: 3585423) +WrapTest:test_wrap_arbitrum(uint32) (runs: 258, μ: 4159384, ~: 4159384) +WrapTest:test_wrap_base(uint32) (runs: 258, μ: 4069009, ~: 4069009) +ZapInTest:test_zap_in_arbitrum(uint32) (runs: 259, μ: 4292469, ~: 4292469) +ZapInTest:test_zap_in_base(uint32) (runs: 259, μ: 4189486, ~: 4189486) +ZapOutTest:test_zap_out_arbitum(uint32) (runs: 258, μ: 4242613, ~: 4242613) +ZapOutTest:test_zap_out_base(uint32) (runs: 258, μ: 4144528, ~: 4144528) \ No newline at end of file diff --git a/lcov.info b/lcov.info index f17dbbc..ca81419 100644 --- a/lcov.info +++ b/lcov.info @@ -1,252 +1,319 @@ TN: SF:script/Deploy.s.sol -FN:49,DeployBaseGoerli.run -FNDA:0,DeployBaseGoerli.run -DA:50,0 -DA:51,0 -DA:53,0 -DA:57,0 -FN:33,DeployBase.run -FNDA:0,DeployBase.run -DA:34,0 -DA:35,0 -DA:37,0 -DA:41,0 -FN:18,Setup.deploySystem +FN:13,Setup.deploySystem FNDA:0,Setup.deploySystem -DA:24,22 -FN:82,DeployOptimismGoerli.run -FNDA:0,DeployOptimismGoerli.run -DA:83,0 -DA:84,0 -DA:86,0 -DA:90,0 -FN:65,DeployOptimism.run -FNDA:0,DeployOptimism.run -DA:66,0 -DA:67,0 -DA:69,0 -DA:73,0 -FNF:5 -FNH:0 -LF:17 -LH:1 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/Zap.sol -FN:98,Zap._zapIn -FNDA:0,Zap._zapIn -DA:104,0 -BRDA:104,0,0,- -BRDA:104,0,1,- -DA:105,0 -DA:111,0 -BRDA:111,1,0,- -BRDA:111,1,1,- -DA:112,0 -DA:127,0 -DA:132,0 -DA:139,0 -BRDA:139,2,0,- -BRDA:139,2,1,- -DA:140,0 -DA:151,0 -DA:158,0 -FN:173,Zap._zapOut -FNDA:0,Zap._zapOut -DA:187,0 -BRDA:187,3,0,- -BRDA:187,3,1,- -DA:188,0 -DA:192,0 -BRDA:192,4,0,- -BRDA:192,4,1,- -DA:193,0 -DA:204,0 -DA:212,0 -BRDA:212,5,0,- -BRDA:212,5,1,- -DA:213,0 -DA:227,0 -DA:232,0 -DA:238,0 -FNF:2 -FNH:0 -LF:20 -LH:0 -BRF:12 -BRH:0 -end_of_record -TN: -SF:test/Zap.t.sol -FN:50,ZapTest.setUp -FNDA:0,ZapTest.setUp -DA:51,0 +DA:27,0 +FN:48,DeployBase.run +FNDA:0,DeployBase.run +DA:49,0 +DA:50,0 DA:52,0 -DA:54,0 -DA:55,0 -DA:56,0 -DA:57,0 -DA:59,0 -DA:60,0 -DA:61,0 -DA:63,0 DA:64,0 -DA:66,0 -DA:68,0 -DA:69,0 -DA:71,0 -DA:73,0 -FNF:1 +FN:76,DeployArbitrum.run +FNDA:0,DeployArbitrum.run +DA:77,0 +DA:78,0 +DA:80,0 +DA:92,0 +FN:104,DeployArbitrumSepolia.run +FNDA:0,DeployArbitrumSepolia.run +DA:105,0 +DA:106,0 +DA:108,0 +DA:120,0 +FNF:4 FNH:0 -LF:16 +LF:13 LH:0 BRF:0 BRH:0 end_of_record TN: -SF:test/utils/Bootstrap.sol -FN:27,Bootstrap.initializeLocal -FNDA:0,Bootstrap.initializeLocal -DA:28,0 -DA:29,0 -DA:31,0 -FN:34,Bootstrap.initializeBase -FNDA:0,Bootstrap.initializeBase -DA:35,0 -DA:36,0 -DA:38,0 -FN:41,Bootstrap.initializeBaseGoerli -FNDA:0,Bootstrap.initializeBaseGoerli -DA:42,0 -DA:43,0 -DA:45,0 -FN:61,BootstrapBase.init -FNDA:22,BootstrapBase.init -DA:62,22 -FN:69,BootstrapBaseGoerli.init -FNDA:0,BootstrapBaseGoerli.init -DA:70,0 -FN:50,BootstrapLocal.init -FNDA:0,BootstrapLocal.init -DA:51,0 -FNF:6 -FNH:1 -LF:12 -LH:1 -BRF:0 -BRH:0 +SF:src/Zap.sol +FN:46,Zap. +FNDA:3636,Zap. +DA:58,3636 +DA:61,3636 +DA:62,3636 +DA:63,3636 +DA:64,3636 +DA:65,3636 +DA:66,3636 +DA:67,3636 +DA:68,3636 +DA:69,3636 +DA:70,3636 +DA:73,3636 +DA:74,3636 +DA:77,3636 +DA:78,3636 +FN:87,Zap.isAuthorized +FNDA:2,Zap.isAuthorized +DA:88,2 +DA:91,2 +BRDA:91,0,0,2 +BRDA:91,0,1,2 +FN:96,Zap.onlyAave +FNDA:528,Zap.onlyAave +DA:97,528 +BRDA:97,1,0,528 +BRDA:97,1,1,- +FN:111,Zap.zapIn +FNDA:518,Zap.zapIn +DA:119,518 +DA:120,518 +DA:121,518 +FN:126,Zap._zapIn +FNDA:518,Zap._zapIn +DA:133,518 +DA:134,518 +FN:143,Zap.zapOut +FNDA:516,Zap.zapOut +DA:151,516 +DA:152,516 +DA:153,516 +FN:158,Zap._zapOut +FNDA:516,Zap._zapOut +DA:165,516 +DA:166,516 +FN:182,Zap.wrap +FNDA:1032,Zap.wrap +DA:192,1032 +DA:193,1032 +DA:194,1032 +FN:199,Zap._wrap +FNDA:1550,Zap._wrap +DA:208,1550 +DA:209,1550 +DA:213,1550 +DA:214,1550 +DA:215,0 +DA:216,0 +FN:229,Zap.unwrap +FNDA:516,Zap.unwrap +DA:239,516 +DA:240,516 +DA:241,516 +DA:242,516 +FN:247,Zap._unwrap +FNDA:1032,Zap._unwrap +DA:255,1032 +DA:256,1032 +DA:257,1032 +DA:261,1032 +DA:262,1032 +DA:263,0 +DA:264,0 +FN:279,Zap.buy +FNDA:1032,Zap.buy +DA:288,1032 +DA:289,1032 +DA:290,1032 +FN:295,Zap._buy +FNDA:1548,Zap._buy +DA:303,1548 +DA:304,1548 +DA:309,1548 +DA:310,1548 +DA:311,1548 +DA:312,0 +DA:313,0 +FN:324,Zap.sell +FNDA:516,Zap.sell +DA:333,516 +DA:334,516 +DA:335,516 +DA:336,516 +FN:341,Zap._sell +FNDA:1034,Zap._sell +DA:349,1034 +DA:350,1034 +DA:351,1034 +DA:356,1034 +DA:357,1034 +DA:358,0 +DA:359,0 +FN:377,Zap.unwind +FNDA:2,Zap.unwind +DA:390,0 +DA:392,0 +DA:403,0 +DA:405,0 +DA:413,0 +FN:423,Zap.executeOperation +FNDA:528,Zap.executeOperation +DA:435,0 +DA:437,0 +DA:442,0 +DA:443,0 +DA:445,0 +DA:447,0 +FN:456,Zap._unwind +FNDA:0,Zap._unwind +DA:465,0 +DA:472,0 +DA:478,0 +DA:482,0 +DA:486,0 +DA:490,0 +DA:494,0 +DA:498,0 +DA:505,0 +FN:513,Zap._approximateLoanNeeded +FNDA:0,Zap._approximateLoanNeeded +DA:519,0 +DA:521,0 +DA:522,0 +DA:527,0 +DA:532,0 +FN:545,Zap.burn +FNDA:0,Zap.burn +DA:546,0 +DA:547,0 +DA:548,0 +DA:549,0 +FN:554,Zap._burn +FNDA:0,Zap._burn +DA:555,0 +DA:556,0 +FN:569,Zap.withdraw +FNDA:4,Zap.withdraw +DA:578,2 +DA:579,2 +DA:582,2 +FN:588,Zap._withdraw +FNDA:2,Zap._withdraw +DA:595,2 +DA:596,2 +DA:601,2 +FN:616,Zap.swapFor +FNDA:0,Zap.swapFor +DA:625,0 +DA:626,0 +DA:627,0 +DA:629,0 +BRDA:629,2,0,- +DA:630,0 +FN:636,Zap._swapFor +FNDA:0,Zap._swapFor +DA:644,0 +DA:646,0 +DA:657,0 +DA:659,0 +DA:660,0 +DA:661,0 +DA:662,0 +FN:674,Zap.swapWith +FNDA:0,Zap.swapWith +DA:683,0 +DA:684,0 +DA:685,0 +FN:690,Zap._swapWith +FNDA:0,Zap._swapWith +DA:698,0 +DA:700,0 +DA:711,0 +DA:713,0 +DA:714,0 +DA:715,0 +DA:716,0 +FN:729,Zap._pull +FNDA:4130,Zap._pull +DA:737,4130 +DA:739,4130 +DA:741,4130 +DA:742,4130 +DA:743,4130 +BRDA:743,3,0,- +BRDA:743,3,1,4130 +DA:747,0 +DA:748,0 +FN:757,Zap._push +FNDA:4132,Zap._push +DA:765,4132 +DA:767,4132 +DA:768,4132 +DA:769,4132 +BRDA:769,4,0,- +BRDA:769,4,1,4132 +DA:773,0 +DA:774,0 +FNF:29 +FNH:21 +LF:141 +LH:76 +BRF:9 +BRH:5 end_of_record TN: -SF:test/utils/exposed/ZapExposed.sol -FN:11,ZapExposed.expose_HASHED_SUSDC_NAME -FNDA:0,ZapExposed.expose_HASHED_SUSDC_NAME -DA:12,0 -FN:15,ZapExposed.expose_USDC -FNDA:0,ZapExposed.expose_USDC -DA:16,0 -FN:19,ZapExposed.expose_SUSD -FNDA:0,ZapExposed.expose_SUSD -DA:20,0 -FN:23,ZapExposed.expose_SUSDC -FNDA:0,ZapExposed.expose_SUSDC -DA:24,0 -FN:27,ZapExposed.expose_SUSDC_SPOT_MARKET_ID -FNDA:0,ZapExposed.expose_SUSDC_SPOT_MARKET_ID +SF:src/utils/Reentrancy.sol +FN:27,Reentrancy.requireStage +FNDA:0,Reentrancy.requireStage DA:28,0 -FN:31,ZapExposed.expose_SPOT_MARKET_PROXY -FNDA:0,ZapExposed.expose_SPOT_MARKET_PROXY -DA:32,0 -FN:35,ZapExposed.expose_DECIMALS_FACTOR -FNDA:0,ZapExposed.expose_DECIMALS_FACTOR -DA:36,0 -FN:46,ZapExposed.expose_zapIn -FNDA:0,ZapExposed.expose_zapIn -DA:47,0 -FN:50,ZapExposed.expose_zapOut -FNDA:0,ZapExposed.expose_zapOut -DA:51,0 -FNF:9 -FNH:0 -LF:9 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/utils/mocks/MockSUSD.sol -FN:9,MockSUSD.decimals -FNDA:1,MockSUSD.decimals -DA:10,1 -FN:13,MockSUSD.balanceOf -FNDA:0,MockSUSD.balanceOf -DA:14,0 -FN:17,MockSUSD.transfer -FNDA:0,MockSUSD.transfer -DA:18,0 -FN:21,MockSUSD.approve -FNDA:0,MockSUSD.approve -DA:22,0 -FN:25,MockSUSD.transferFrom -FNDA:0,MockSUSD.transferFrom -DA:31,0 -FNF:5 -FNH:1 -LF:5 -LH:1 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/utils/mocks/MockSpotMarketProxy.sol -FN:9,MockSpotMarketProxy.name -FNDA:1,MockSpotMarketProxy.name -DA:15,1 -FN:18,MockSpotMarketProxy.getSynth -FNDA:0,MockSpotMarketProxy.getSynth -DA:24,0 -FN:27,MockSpotMarketProxy.wrap -FNDA:0,MockSpotMarketProxy.wrap -DA:32,0 -FN:35,MockSpotMarketProxy.unwrap -FNDA:0,MockSpotMarketProxy.unwrap -DA:45,0 -FN:48,MockSpotMarketProxy.buy -FNDA:0,MockSpotMarketProxy.buy -DA:54,0 -FN:57,MockSpotMarketProxy.sell -FNDA:0,MockSpotMarketProxy.sell -DA:68,0 -FNF:6 +FN:32,Reentrancy._requireStage +FNDA:427,Reentrancy._requireStage +DA:33,427 +BRDA:33,0,0,168 +BRDA:33,0,1,259 +FNF:2 FNH:1 -LF:6 +LF:2 LH:1 -BRF:0 -BRH:0 +BRF:2 +BRH:2 end_of_record TN: -SF:test/utils/mocks/MockUSDC.sol -FN:9,MockUSDC.decimals -FNDA:0,MockUSDC.decimals -DA:10,0 -FN:13,MockUSDC.balanceOf -FNDA:0,MockUSDC.balanceOf -DA:14,0 -FN:17,MockUSDC.transfer -FNDA:0,MockUSDC.transfer -DA:18,0 -FN:21,MockUSDC.approve -FNDA:0,MockUSDC.approve -DA:22,0 -FN:25,MockUSDC.transferFrom -FNDA:0,MockUSDC.transferFrom -DA:31,0 +SF:test/utils/Bootstrap.sol +FN:35,Bootstrap.setUp +FNDA:33,Bootstrap.setUp +DA:36,33 +DA:37,33 +DA:39,33 +DA:40,33 +DA:41,33 +FN:44,Bootstrap.base +FNDA:1817,Bootstrap.base +DA:45,1817 +DA:46,1817 +DA:57,1817 +DA:58,1817 +DA:59,1817 +DA:60,1817 +DA:61,1817 +DA:62,1817 +DA:63,1817 +FN:67,Bootstrap.arbitrum +FNDA:1817,Bootstrap.arbitrum +DA:68,1817 +DA:69,1817 +DA:80,1817 +DA:81,1817 +DA:82,1817 +DA:83,1817 +DA:84,1817 +DA:85,1817 +DA:86,1817 +FN:90,Bootstrap.arbitrum_b +FNDA:0,Bootstrap.arbitrum_b +DA:91,0 +DA:92,0 +DA:103,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:109,0 +FN:113,Bootstrap._spin +FNDA:3100,Bootstrap._spin +DA:121,3100 +DA:122,3100 +DA:123,3100 +DA:124,3100 FNF:5 -FNH:0 -LF:5 -LH:0 +FNH:4 +LF:36 +LH:27 BRF:0 BRH:0 end_of_record From a03fd43708bdce953692f1c4410a792ea570be37 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Mon, 30 Sep 2024 17:24:52 -0400 Subject: [PATCH 070/129] =?UTF-8?q?=F0=9F=91=B7=20Remedy=20misassignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 45 +++++++++++++++++++++++++++++++-------------- test/Unwind.t.sol | 4 ++-- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index e68644a..c878f9b 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -370,6 +370,7 @@ contract Zap is Reentrancy, Errors { /// @param _accountId synthetix perp market account id /// @param _collateralId synthetix market id of collateral /// @param _collateralAmount amount of collateral to unwind + /// @param _collateral address of collateral to unwind /// @param _zapTolerance acceptable slippage for zapping /// @param _unwrapTolerance acceptable slippage for unwrapping /// @param _swapTolerance acceptable slippage for swapping @@ -378,6 +379,7 @@ contract Zap is Reentrancy, Errors { uint128 _accountId, uint128 _collateralId, uint256 _collateralAmount, + address _collateral, uint256 _zapTolerance, uint256 _unwrapTolerance, uint256 _swapTolerance, @@ -393,6 +395,7 @@ contract Zap is Reentrancy, Errors { _accountId, _collateralId, _collateralAmount, + _collateral, _zapTolerance, _unwrapTolerance, _swapTolerance, @@ -434,15 +437,23 @@ contract Zap is Reentrancy, Errors { { stage = Stage.LEVEL2; - (,,,,,, address _receiver) = abi.decode( + (,,, address _collateral,,,, address _receiver) = abi.decode( _params, - (uint128, uint128, uint256, uint256, uint256, uint256, address) + ( + uint128, + uint128, + uint256, + address, + uint256, + uint256, + uint256, + address + ) ); - (uint256 unwound, address collateral) = - _unwind(_flashloan, _premium, _params); + uint256 unwound = _unwind(_flashloan, _premium, _params); - _push(collateral, _receiver, unwound); + _push(_collateral, _receiver, unwound); return IERC20(USDC).approve(AAVE, _flashloan + _premium); } @@ -452,7 +463,6 @@ contract Zap is Reentrancy, Errors { /// @param _premium amount of USDC premium owed to Aave /// @param _params encoded parameters for unwinding synthetix perp position /// @return unwound amount of collateral - /// @return collateral address function _unwind( uint256 _flashloan, uint256 _premium, @@ -460,18 +470,29 @@ contract Zap is Reentrancy, Errors { ) internal requireStage(Stage.LEVEL2) - returns (uint256 unwound, address collateral) + returns (uint256 unwound) { ( uint128 _accountId, uint128 _collateralId, uint256 _collateralAmount, + address _collateral, uint256 _zapTolerance, uint256 _unwrapTolerance, uint256 _swapTolerance, + /* address _receiver */ ) = abi.decode( _params, - (uint128, uint128, uint256, uint256, uint256, uint256, address) + ( + uint128, + uint128, + uint256, + address, + uint256, + uint256, + uint256, + address + ) ); // zap USDC from flashloan into USDx @@ -489,10 +510,6 @@ contract Zap is Reentrancy, Errors { // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) unwound = _unwrap(_collateralId, _collateralAmount, _unwrapTolerance); - // establish unwrapped collateral address - // i.e., WETH, USDe, USDC (...) - collateral = ISpotMarket(SPOT_MARKET).getSynth(_collateralId); - // establish total debt now owed to Aave; // i.e., # of USDC _flashloan += _premium; @@ -502,9 +519,9 @@ contract Zap is Reentrancy, Errors { // i.e., USDe -(swap)-> USDC -(repay)-> Aave // i.e., USDC -(repay)-> Aave // whatever collateral amount is remaining is returned to the caller - unwound -= collateral == USDC + unwound -= _collateral == USDC ? _flashloan - : _swapFor(collateral, _flashloan, _swapTolerance); + : _swapFor(_collateral, _flashloan, _swapTolerance); } /// @notice approximate USDC needed to unwind synthetix perp position diff --git a/test/Unwind.t.sol b/test/Unwind.t.sol index 6efd69c..3015b1b 100644 --- a/test/Unwind.t.sol +++ b/test/Unwind.t.sol @@ -19,13 +19,13 @@ contract UnwindTest is Bootstrap, Errors { function test_unwind_is_authorized_arbitrum() public arbitrum { vm.prank(ACTOR); vm.expectRevert(NotPermitted.selector); - zap.unwind(0, 0, 0, 0, 0, 0, address(0)); + zap.unwind(0, 0, 0, address(0), 0, 0, 0, address(0)); } function test_unwind_is_authorized_base() public base { vm.prank(ACTOR); vm.expectRevert(NotPermitted.selector); - zap.unwind(0, 0, 0, 0, 0, 0, address(0)); + zap.unwind(0, 0, 0, address(0), 0, 0, 0, address(0)); } /// @custom:todo From fe873104d39838d90c8abb71e871b4e7393dc50c Mon Sep 17 00:00:00 2001 From: jaredborders Date: Mon, 30 Sep 2024 17:26:49 -0400 Subject: [PATCH 071/129] =?UTF-8?q?=F0=9F=93=9A=20fix=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Zap.sol b/src/Zap.sol index c878f9b..1016408 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -540,7 +540,7 @@ contract Zap is Reentrancy, Errors { /// @custom:synthetix debt is denominated in USDx /// @custom:aave debt is denominated in USDC - /// @dev scale loan samount accordingly + /// @dev scale loan amount accordingly amount /= 10 ** (usdxDecimals - usdcDecimals); /// @dev barring exceptional circumstances, From 7b77087d5eed9527ae7b6ecd9e1d165e8c63a1e3 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Mon, 30 Sep 2024 17:29:16 -0400 Subject: [PATCH 072/129] =?UTF-8?q?=F0=9F=91=B7=20fix=20burn=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Zap.sol b/src/Zap.sol index 1016408..6c45a3c 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -563,7 +563,7 @@ contract Zap is Reentrancy, Errors { _pull(USDX, msg.sender, _amount); _burn(_amount, _accountId); uint256 remaining = IERC20(USDX).balanceOf(address(this)); - _push(USDX, msg.sender, remaining); + if (remaining > 0) _push(USDX, msg.sender, remaining); } /// @dev allowance is assumed From 3ebb3dce1e997d0b97aa541c0b6b1ddaf851acae Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 14:55:35 -0400 Subject: [PATCH 073/129] =?UTF-8?q?=F0=9F=91=B7=20add=20uniswap=20quoter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/IUniswap.sol | 41 +++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/interfaces/IUniswap.sol b/src/interfaces/IUniswap.sol index 847b0ae..c4109ec 100644 --- a/src/interfaces/IUniswap.sol +++ b/src/interfaces/IUniswap.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -/// @custom:todo add natspec -interface IUniswap { +interface IRouter { struct ExactInputSingleParams { address tokenIn; @@ -35,3 +34,41 @@ interface IUniswap { returns (uint256 amountIn); } + +interface IQuoter { + + struct QuoteExactInputSingleParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + function quoteExactInputSingle(QuoteExactInputSingleParams memory params) + external + returns ( + uint256 amountOut, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ); + + struct QuoteExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint256 amount; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) + external + returns ( + uint256 amountIn, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ); + +} From 9aba86e68c8777eb82a420e1f4222d940f0636b5 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 14:55:58 -0400 Subject: [PATCH 074/129] =?UTF-8?q?=F0=9F=91=B7=20add=20quoteSwapFor=20and?= =?UTF-8?q?=20quoteSwapWith?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 97 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 14 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 6c45a3c..dbcbdf0 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.27; import {IPool} from "./interfaces/IAave.sol"; import {IERC20} from "./interfaces/IERC20.sol"; import {ICore, IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; -import {IUniswap} from "./interfaces/IUniswap.sol"; +import {IQuoter, IRouter} from "./interfaces/IUniswap.sol"; import {Errors} from "./utils/Errors.sol"; import {Reentrancy} from "./utils/Reentrancy.sol"; @@ -40,7 +40,8 @@ contract Zap is Reentrancy, Errors { uint16 public immutable REFERRAL_CODE; /// @custom:uniswap - address public immutable UNISWAP; + address public immutable ROUTER; + address public immutable QUOTER; uint24 public immutable FEE_TIER; constructor( @@ -52,7 +53,8 @@ contract Zap is Reentrancy, Errors { address _referrer, uint128 _susdcSpotId, address _aave, - address _uniswap + address _router, + address _quoter ) { /// @custom:circle USDC = _usdc; @@ -74,7 +76,8 @@ contract Zap is Reentrancy, Errors { REFERRAL_CODE = 0; /// @custom:uniswap - UNISWAP = _uniswap; + ROUTER = _router; + QUOTER = _quoter; FEE_TIER = 3000; } @@ -622,6 +625,74 @@ contract Zap is Reentrancy, Errors { UNISWAP //////////////////////////////////////////////////////////////*/ + /// @notice query amount required to receive a specific amount of token + /// @dev quoting is NOT gas efficient and should NOT be called on chain + /// @custom:integrator quoting function inclusion is for QoL purposes + /// @param _tokenIn address of token being swapped in + /// @param _tokenOut address of token being swapped out + /// @param _amountOut is the desired output amount + /// @param _fee of the token pool to consider for the pair + /// @param _sqrtPriceLimitX96 of the pool; cannot be exceeded for swap + /// @return amountIn required as the input for the swap in order + /// @return sqrtPriceX96After of the pool after the swap + /// @return initializedTicksCrossed during the quoted swap + /// @return gasEstimate of gas that the swap will consume + function quoteSwapFor( + address _tokenIn, + address _tokenOut, + uint256 _amountOut, + uint24 _fee, + uint160 _sqrtPriceLimitX96 + ) + external + returns ( + uint256 amountIn, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ) + { + return IQuoter(QUOTER).quoteExactOutputSingle( + IQuoter.QuoteExactOutputSingleParams( + _tokenIn, _tokenOut, _amountOut, _fee, _sqrtPriceLimitX96 + ) + ); + } + + /// @notice query amount received for a specific amount of token to spend + /// @dev quoting is NOT gas efficient and should NOT be called on chain + /// @custom:integrator quoting function inclusion is for QoL purposes + /// @param _tokenIn address of token being swapped in + /// @param _tokenOut address of token being swapped out + /// @param _amountIn is the input amount to spend + /// @param _fee of the token pool to consider for the pair + /// @param _sqrtPriceLimitX96 of the pool; cannot be exceeded for swap + /// @return amountOut received as the output for the swap in order + /// @return sqrtPriceX96After of the pool after the swap + /// @return initializedTicksCrossed during the quoted swap + /// @return gasEstimate of gas that the swap will consume + function quoteSwapWith( + address _tokenIn, + address _tokenOut, + uint256 _amountIn, + uint24 _fee, + uint160 _sqrtPriceLimitX96 + ) + external + returns ( + uint256 amountOut, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ) + { + return IQuoter(QUOTER).quoteExactInputSingle( + IQuoter.QuoteExactInputSingleParams( + _tokenIn, _tokenOut, _amountIn, _fee, _sqrtPriceLimitX96 + ) + ); + } + /// @notice swap a tolerable amount of tokens for a specific amount of USDC /// @dev caller must grant token allowance to this contract /// @dev any excess token not spent will be returned to the caller @@ -658,9 +729,9 @@ contract Zap is Reentrancy, Errors { internal returns (uint256 deducted) { - IERC20(_from).approve(UNISWAP, _tolerance); + IERC20(_from).approve(ROUTER, _tolerance); - IUniswap.ExactOutputSingleParams memory params = IUniswap + IRouter.ExactOutputSingleParams memory params = IRouter .ExactOutputSingleParams({ tokenIn: _from, tokenOut: USDC, @@ -671,9 +742,8 @@ contract Zap is Reentrancy, Errors { sqrtPriceLimitX96: 0 }); - try IUniswap(UNISWAP).exactOutputSingle(params) returns ( - uint256 amountIn - ) { + try IRouter(ROUTER).exactOutputSingle(params) returns (uint256 amountIn) + { deducted = amountIn; } catch Error(string memory reason) { revert SwapFailed(reason); @@ -712,9 +782,9 @@ contract Zap is Reentrancy, Errors { internal returns (uint256 received) { - IERC20(_from).approve(UNISWAP, _amount); + IERC20(_from).approve(ROUTER, _amount); - IUniswap.ExactInputSingleParams memory params = IUniswap + IRouter.ExactInputSingleParams memory params = IRouter .ExactInputSingleParams({ tokenIn: _from, tokenOut: USDC, @@ -725,9 +795,8 @@ contract Zap is Reentrancy, Errors { sqrtPriceLimitX96: 0 }); - try IUniswap(UNISWAP).exactInputSingle(params) returns ( - uint256 amountOut - ) { + try IRouter(ROUTER).exactInputSingle(params) returns (uint256 amountOut) + { received = amountOut; } catch Error(string memory reason) { revert SwapFailed(reason); From 7769bb1d7ce4d25b7fd5760381d6426f1102fb38 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 14:56:22 -0400 Subject: [PATCH 075/129] =?UTF-8?q?=F0=9F=9A=80=20add=20quoter=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/utils/Parameters.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/script/utils/Parameters.sol b/script/utils/Parameters.sol index 84b14fb..01fd6e6 100644 --- a/script/utils/Parameters.sol +++ b/script/utils/Parameters.sol @@ -16,7 +16,8 @@ contract Base { address BASE_AAVE_POOL = 0xA238Dd80C259a72e81d7e4664a9801593F98d1c5; /// @custom:uniswap - address BASE_UNISWAP = 0x2626664c2603336E57B271c5C0b26F421741e481; + address BASE_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481; + address BASE_QUOTER = 0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a; } @@ -35,7 +36,8 @@ contract Arbitrum { address ARBITRUM_AAVE_POOL = 0x794a61358D6845594F94dc1DB02A252b5b4814aD; /// @custom:uniswap - address ARBITRUM_UNISWAP = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; + address ARBITRUM_ROUTER = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; + address ARBITRUM_QUOTER = 0x61fFE014bA17989E743c5F6cB21bF9697530B21e; } @@ -57,7 +59,7 @@ contract ArbitrumSepolia { 0xBfC91D59fdAA134A4ED45f7B584cAf96D7792Eff; /// @custom:uniswap - address ARBITRUM_SEPOLIA_UNISWAP = - 0x101F443B4d1b059569D643917553c771E1b9663E; + address ARBITRUM_SEPOLIA_ROUTER = 0x101F443B4d1b059569D643917553c771E1b9663E; + address ARBITRUM_SEPOLIA_QUOTER = 0x2779a0CC1c3e0E44D2542EC3e79e3864Ae93Ef0B; } From 3576683905fb985f718bf708027fe9b24615ada1 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 14:56:34 -0400 Subject: [PATCH 076/129] =?UTF-8?q?=F0=9F=9A=80=20update=20deploy=20script?= =?UTF-8?q?=20with=20quoter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 1f878a0..fa8ba7a 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -6,10 +6,10 @@ import {Zap} from "../src/Zap.sol"; import {Arbitrum, ArbitrumSepolia, Base} from "./utils/Parameters.sol"; /// @title Zap deployment script -/// @author JaredBorders (jaredborders@pm.me) +/// @author @jaredborders +/// @author @Flocqst contract Setup is Script { - /// @custom:todo function deploySystem( address usdc, address usdx, @@ -19,7 +19,8 @@ contract Setup is Script { address referrer, uint128 susdcSpotId, address aave, - address uniswap + address router, + address quoter ) public returns (Zap zap) @@ -33,7 +34,8 @@ contract Setup is Script { _referrer: referrer, _susdcSpotId: susdcSpotId, _aave: aave, - _uniswap: uniswap + _router: router, + _quoter: quoter }); } @@ -58,7 +60,8 @@ contract DeployBase is Setup, Base { referrer: BASE_REFERRER, susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, aave: BASE_AAVE_POOL, - uniswap: BASE_UNISWAP + router: BASE_ROUTER, + quoter: BASE_QUOTER }); vm.stopBroadcast(); @@ -86,7 +89,8 @@ contract DeployArbitrum is Setup, Arbitrum { referrer: ARBITRUM_REFERRER, susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, aave: ARBITRUM_AAVE_POOL, - uniswap: ARBITRUM_UNISWAP + router: ARBITRUM_ROUTER, + quoter: ARBITRUM_QUOTER }); vm.stopBroadcast(); @@ -114,7 +118,8 @@ contract DeployArbitrumSepolia is Setup, ArbitrumSepolia { referrer: ARBITRUM_SEPOLIA_REFERRER, susdcSpotId: ARBITRUM_SEPOLIAS_USDC_SPOT_MARKET_ID, aave: ARBITRUM_SEPOLIA_AAVE_POOL, - uniswap: ARBITRUM_SEPOLIA_UNISWAP + router: ARBITRUM_SEPOLIA_ROUTER, + quoter: ARBITRUM_SEPOLIA_QUOTER }); vm.stopBroadcast(); From a948df4dbea203e832034db15b5d72d2aedb1a06 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:37:37 -0400 Subject: [PATCH 077/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20add=20arbitrum=20s?= =?UTF-8?q?epolia=20rpc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env-example b/.env-example index 5da1691..28a50b6 100644 --- a/.env-example +++ b/.env-example @@ -1,5 +1,6 @@ BASE_RPC=https://base-mainnet.g.alchemy.com/v2/KEY ARBITRUM_RPC=https://arb-mainnet.g.alchemy.com/v2/KEY +ARBITRUM_SEPOLIA_RPC=https://arb-sepolia.g.alchemy.com/v2/KEY PRIVATE_KEY=KEY BASESCAN_API_KEY=KEY ARBISCAN_API_KEY=KEY \ No newline at end of file From 5aa0c68d94403f6bda0e5af3689c8fcef74c7b6d Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:37:54 -0400 Subject: [PATCH 078/129] =?UTF-8?q?=F0=9F=9A=80=20update=20deployment=20pr?= =?UTF-8?q?ocedure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 53 ++++++++++++++----------------------- script/utils/Parameters.sol | 7 ++--- 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index fa8ba7a..6948fe3 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -5,17 +5,23 @@ import {Script} from "../lib/forge-std/src/Script.sol"; import {Zap} from "../src/Zap.sol"; import {Arbitrum, ArbitrumSepolia, Base} from "./utils/Parameters.sol"; -/// @title Zap deployment script +/// @title zap deployment script /// @author @jaredborders -/// @author @Flocqst -contract Setup is Script { +/// @author @flocqst +contract Deploy is Script { + + modifier broadcast() { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(privateKey); + _; + vm.stopBroadcast(); + } function deploySystem( address usdc, address usdx, address spotMarket, address perpsMarket, - address core, address referrer, uint128 susdcSpotId, address aave, @@ -30,7 +36,6 @@ contract Setup is Script { _usdx: usdx, _spotMarket: spotMarket, _perpsMarket: perpsMarket, - _core: core, _referrer: referrer, _susdcSpotId: susdcSpotId, _aave: aave, @@ -45,26 +50,20 @@ contract Setup is Script { /// (1) load the variables in the .env file via `source .env` /// (2) run `forge script script/Deploy.s.sol:DeployBase --rpc-url $BASE_RPC_URL /// --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv` -contract DeployBase is Setup, Base { +contract DeployBase is Deploy, Base { - function run() public { - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(privateKey); - - Setup.deploySystem({ + function run() public broadcast { + deploySystem({ usdc: BASE_USDC, usdx: BASE_USDX, spotMarket: BASE_SPOT_MARKET, perpsMarket: BASE_PERPS_MARKET, - core: BASE_CORE, referrer: BASE_REFERRER, susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, aave: BASE_AAVE_POOL, router: BASE_ROUTER, quoter: BASE_QUOTER }); - - vm.stopBroadcast(); } } @@ -74,26 +73,20 @@ contract DeployBase is Setup, Base { /// (2) run `forge script script/Deploy.s.sol:DeployArbitrum --rpc-url /// $ARBITRUM_RPC_URL /// --etherscan-api-key $ARBITRUM_RPC_URL --broadcast --verify -vvvv` -contract DeployArbitrum is Setup, Arbitrum { +contract DeployArbitrum is Deploy, Arbitrum { - function run() public { - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(privateKey); - - Setup.deploySystem({ + function run() public broadcast { + deploySystem({ usdc: ARBITRUM_USDC, usdx: ARBITRUM_USDX, spotMarket: ARBITRUM_SPOT_MARKET, perpsMarket: ARBITRUM_PERPS_MARKET, - core: ARBITRUM_CORE, referrer: ARBITRUM_REFERRER, susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, aave: ARBITRUM_AAVE_POOL, router: ARBITRUM_ROUTER, quoter: ARBITRUM_QUOTER }); - - vm.stopBroadcast(); } } @@ -103,26 +96,20 @@ contract DeployArbitrum is Setup, Arbitrum { /// (2) run `forge script script/Deploy.s.sol:DeployArbitrumSepolia --rpc-url /// $ARBITRUM_SEPOLIA_RPC_URL /// --etherscan-api-key $ARBITRUM_RPC_URL --broadcast --verify -vvvv` -contract DeployArbitrumSepolia is Setup, ArbitrumSepolia { +contract DeployArbitrumSepolia is Deploy, ArbitrumSepolia { - function run() public { - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(privateKey); - - Setup.deploySystem({ + function run() public broadcast { + deploySystem({ usdc: ARBITRUM_SEPOLIA_USDC, usdx: ARBITRUM_SEPOLIA_USDX, spotMarket: ARBITRUM_SEPOLIA_SPOT_MARKET, perpsMarket: ARBITRUM_SEPOLIA_PERPS_MARKET, - core: ARBITRUM_SEPOLIA_CORE, referrer: ARBITRUM_SEPOLIA_REFERRER, - susdcSpotId: ARBITRUM_SEPOLIAS_USDC_SPOT_MARKET_ID, + susdcSpotId: ARBITRUM_SEPOLIA_SUSDC_SPOT_MARKET_ID, aave: ARBITRUM_SEPOLIA_AAVE_POOL, router: ARBITRUM_SEPOLIA_ROUTER, quoter: ARBITRUM_SEPOLIA_QUOTER }); - - vm.stopBroadcast(); } } diff --git a/script/utils/Parameters.sol b/script/utils/Parameters.sol index 01fd6e6..a1aaced 100644 --- a/script/utils/Parameters.sol +++ b/script/utils/Parameters.sol @@ -8,7 +8,6 @@ contract Base { address BASE_USDX = 0x09d51516F38980035153a554c26Df3C6f51a23C3; address BASE_SPOT_MARKET = 0x18141523403e2595D31b22604AcB8Fc06a4CaA61; address BASE_PERPS_MARKET = 0x0A2AF931eFFd34b81ebcc57E3d3c9B1E1dE1C9Ce; - address BASE_CORE = 0x32C222A9A159782aFD7529c87FA34b96CA72C696; address BASE_REFERRER = address(0); uint128 BASE_SUSDC_SPOT_MARKET_ID = 1; @@ -28,7 +27,6 @@ contract Arbitrum { address ARBITRUM_USDX = 0xb2F30A7C980f052f02563fb518dcc39e6bf38175; address ARBITRUM_SPOT_MARKET = 0xa65538A6B9A8442854dEcB6E3F85782C60757D60; address ARBITRUM_PERPS_MARKET = 0xd762960c31210Cf1bDf75b06A5192d395EEDC659; - address ARBITRUM_CORE = 0xffffffaEff0B96Ea8e4f94b2253f31abdD875847; address ARBITRUM_REFERRER = address(0); uint128 ARBITRUM_SUSDC_SPOT_MARKET_ID = 2; @@ -45,14 +43,13 @@ contract ArbitrumSepolia { /// @custom:synthetix address ARBITRUM_SEPOLIA_USDC = 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d; - address ARBITRUM_SEPOLIA_USDX = 0xb645d694D424D091Aa5BA893Eafe85b381bd82Eb; + address ARBITRUM_SEPOLIA_USDX = 0xe487Ad4291019b33e2230F8E2FB1fb6490325260; address ARBITRUM_SEPOLIA_SPOT_MARKET = 0x93d645c42A0CA3e08E9552367B8c454765fff041; address ARBITRUM_SEPOLIA_PERPS_MARKET = 0xA73A7B754Ec870b3738D0654cA75b7d0eEbdb460; - address ARBITRUM_SEPOLIA_CORE = 0x76490713314fCEC173f44e99346F54c6e92a8E42; address ARBITRUM_SEPOLIA_REFERRER = address(0); - uint128 ARBITRUM_SEPOLIAS_USDC_SPOT_MARKET_ID = 2; + uint128 ARBITRUM_SEPOLIA_SUSDC_SPOT_MARKET_ID = 2; /// @custom:aave address ARBITRUM_SEPOLIA_AAVE_POOL = From 4cdf24712866c0c5e85167df99e92050e3c48ea8 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:38:35 -0400 Subject: [PATCH 079/129] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20remove=20unused?= =?UTF-8?q?=20immutable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index dbcbdf0..fcae920 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -3,20 +3,20 @@ pragma solidity 0.8.27; import {IPool} from "./interfaces/IAave.sol"; import {IERC20} from "./interfaces/IERC20.sol"; -import {ICore, IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; +import {IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; import {IQuoter, IRouter} from "./interfaces/IUniswap.sol"; import {Errors} from "./utils/Errors.sol"; import {Reentrancy} from "./utils/Reentrancy.sol"; -/// @title Zap -/// @custom:synthetix Zap USDC into and out of USDx -/// @custom:aave Flashloan USDC to unwind synthetix collateral -/// @custom:uniswap Swap unwound collateral for USDC to repay flashloan -/// @dev Idle token balances are not safe -/// @dev Intended for standalone use; do not inherit +/// @title zap +/// @custom:synthetix zap USDC into and out of USDx +/// @custom:aave flash loan USDC to unwind synthetix collateral +/// @custom:uniswap swap unwound collateral for USDC to repay flashloan +/// @dev idle token balances are not safe +/// @dev intended for standalone use; do not inherit /// @author @jaredborders +/// @author @flocqst /// @author @barrasso -/// @author @Flocqst /// @author @moss-eth contract Zap is Reentrancy, Errors { @@ -30,7 +30,6 @@ contract Zap is Reentrancy, Errors { address public immutable CORE; address public immutable REFERRER; uint128 public immutable SUSDC_SPOT_ID; - uint128 public immutable PREFFERED_POOL_ID; bytes32 public immutable MODIFY_PERMISSION; bytes32 public immutable BURN_PERMISSION; uint128 public immutable USDX_ID; @@ -49,7 +48,6 @@ contract Zap is Reentrancy, Errors { address _usdx, address _spotMarket, address _perpsMarket, - address _core, address _referrer, uint128 _susdcSpotId, address _aave, @@ -63,10 +61,8 @@ contract Zap is Reentrancy, Errors { USDX = _usdx; SPOT_MARKET = _spotMarket; PERPS_MARKET = _perpsMarket; - CORE = _core; REFERRER = _referrer; SUSDC_SPOT_ID = _susdcSpotId; - PREFFERED_POOL_ID = ICore(CORE).getPreferredPool(); MODIFY_PERMISSION = "PERPS_MODIFY_COLLATERAL"; BURN_PERMISSION = "BURN"; USDX_ID = 0; From ebcdf3dcb786b1fd144dbcf537f818b93ef5cc08 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:39:11 -0400 Subject: [PATCH 080/129] =?UTF-8?q?=F0=9F=A7=B9=20clean=20up=20interfaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/IAave.sol | 1519 --------------------------------- src/interfaces/IERC20.sol | 25 - src/interfaces/ISynthetix.sol | 1158 +------------------------ 3 files changed, 4 insertions(+), 2698 deletions(-) diff --git a/src/interfaces/IAave.sol b/src/interfaces/IAave.sol index 8bd5ec3..125ac73 100644 --- a/src/interfaces/IAave.sol +++ b/src/interfaces/IAave.sol @@ -1,1146 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -/// @custom:todo remove extraneous code -library DataTypes { - - struct ReserveData { - //stores the reserve configuration - ReserveConfigurationMap configuration; - //the liquidity index. Expressed in ray - uint128 liquidityIndex; - //the current supply rate. Expressed in ray - uint128 currentLiquidityRate; - //variable borrow index. Expressed in ray - uint128 variableBorrowIndex; - //the current variable borrow rate. Expressed in ray - uint128 currentVariableBorrowRate; - //the current stable borrow rate. Expressed in ray - uint128 currentStableBorrowRate; - //timestamp of last update - uint40 lastUpdateTimestamp; - //the id of the reserve. Represents the position in the list of the - // active reserves - uint16 id; - //aToken address - address aTokenAddress; - //stableDebtToken address - address stableDebtTokenAddress; - //variableDebtToken address - address variableDebtTokenAddress; - //address of the interest rate strategy - address interestRateStrategyAddress; - //the current treasury balance, scaled - uint128 accruedToTreasury; - //the outstanding unbacked aTokens minted through the bridging feature - uint128 unbacked; - //the outstanding debt borrowed against this asset in isolation mode - uint128 isolationModeTotalDebt; - } - - struct ReserveConfigurationMap { - //bit 0-15: LTV - //bit 16-31: Liq. threshold - //bit 32-47: Liq. bonus - //bit 48-55: Decimals - //bit 56: reserve is active - //bit 57: reserve is frozen - //bit 58: borrowing is enabled - //bit 59: stable rate borrowing enabled - //bit 60: asset is paused - //bit 61: borrowing in isolation mode is enabled - //bit 62: siloed borrowing enabled - //bit 63: flashloaning enabled - //bit 64-79: reserve factor - //bit 80-115 borrow cap in whole tokens, borrowCap == 0 => no cap - //bit 116-151 supply cap in whole tokens, supplyCap == 0 => no cap - //bit 152-167 liquidation protocol fee - //bit 168-175 eMode category - //bit 176-211 unbacked mint cap in whole tokens, unbackedMintCap == 0 => - // minting disabled - //bit 212-251 debt ceiling for isolation mode with - // (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals - //bit 252-255 unused - uint256 data; - } - - struct UserConfigurationMap { - /** - * @dev Bitmap of the users collaterals and borrows. It is divided in - * pairs of bits, one pair per asset. - * The first bit indicates if an asset is used as collateral by the - * user, the second whether an - * asset is borrowed by the user. - */ - uint256 data; - } - - struct EModeCategory { - // each eMode category has a custom ltv and liquidation threshold - uint16 ltv; - uint16 liquidationThreshold; - uint16 liquidationBonus; - // each eMode category may or may not have a custom oracle to override - // the individual assets price oracles - address priceSource; - string label; - } - - enum InterestRateMode { - NONE, - STABLE, - VARIABLE - } - - struct ReserveCache { - uint256 currScaledVariableDebt; - uint256 nextScaledVariableDebt; - uint256 currPrincipalStableDebt; - uint256 currAvgStableBorrowRate; - uint256 currTotalStableDebt; - uint256 nextAvgStableBorrowRate; - uint256 nextTotalStableDebt; - uint256 currLiquidityIndex; - uint256 nextLiquidityIndex; - uint256 currVariableBorrowIndex; - uint256 nextVariableBorrowIndex; - uint256 currLiquidityRate; - uint256 currVariableBorrowRate; - uint256 reserveFactor; - ReserveConfigurationMap reserveConfiguration; - address aTokenAddress; - address stableDebtTokenAddress; - address variableDebtTokenAddress; - uint40 reserveLastUpdateTimestamp; - uint40 stableDebtLastUpdateTimestamp; - } - - struct ExecuteLiquidationCallParams { - uint256 reservesCount; - uint256 debtToCover; - address collateralAsset; - address debtAsset; - address user; - bool receiveAToken; - address priceOracle; - uint8 userEModeCategory; - address priceOracleSentinel; - } - - struct ExecuteSupplyParams { - address asset; - uint256 amount; - address onBehalfOf; - uint16 referralCode; - } - - struct ExecuteBorrowParams { - address asset; - address user; - address onBehalfOf; - uint256 amount; - InterestRateMode interestRateMode; - uint16 referralCode; - bool releaseUnderlying; - uint256 maxStableRateBorrowSizePercent; - uint256 reservesCount; - address oracle; - uint8 userEModeCategory; - address priceOracleSentinel; - } - - struct ExecuteRepayParams { - address asset; - uint256 amount; - InterestRateMode interestRateMode; - address onBehalfOf; - bool useATokens; - } - - struct ExecuteWithdrawParams { - address asset; - uint256 amount; - address to; - uint256 reservesCount; - address oracle; - uint8 userEModeCategory; - } - - struct ExecuteSetUserEModeParams { - uint256 reservesCount; - address oracle; - uint8 categoryId; - } - - struct FinalizeTransferParams { - address asset; - address from; - address to; - uint256 amount; - uint256 balanceFromBefore; - uint256 balanceToBefore; - uint256 reservesCount; - address oracle; - uint8 fromEModeCategory; - } - - struct FlashloanParams { - address receiverAddress; - address[] assets; - uint256[] amounts; - uint256[] interestRateModes; - address onBehalfOf; - bytes params; - uint16 referralCode; - uint256 flashLoanPremiumToProtocol; - uint256 flashLoanPremiumTotal; - uint256 maxStableRateBorrowSizePercent; - uint256 reservesCount; - address addressesProvider; - uint8 userEModeCategory; - bool isAuthorizedFlashBorrower; - } - - struct FlashloanSimpleParams { - address receiverAddress; - address asset; - uint256 amount; - bytes params; - uint16 referralCode; - uint256 flashLoanPremiumToProtocol; - uint256 flashLoanPremiumTotal; - } - - struct FlashLoanRepaymentParams { - uint256 amount; - uint256 totalPremium; - uint256 flashLoanPremiumToProtocol; - address asset; - address receiverAddress; - uint16 referralCode; - } - - struct CalculateUserAccountDataParams { - UserConfigurationMap userConfig; - uint256 reservesCount; - address user; - address oracle; - uint8 userEModeCategory; - } - - struct ValidateBorrowParams { - ReserveCache reserveCache; - UserConfigurationMap userConfig; - address asset; - address userAddress; - uint256 amount; - InterestRateMode interestRateMode; - uint256 maxStableLoanPercent; - uint256 reservesCount; - address oracle; - uint8 userEModeCategory; - address priceOracleSentinel; - bool isolationModeActive; - address isolationModeCollateralAddress; - uint256 isolationModeDebtCeiling; - } - - struct ValidateLiquidationCallParams { - ReserveCache debtReserveCache; - uint256 totalDebt; - uint256 healthFactor; - address priceOracleSentinel; - } - - struct CalculateInterestRatesParams { - uint256 unbacked; - uint256 liquidityAdded; - uint256 liquidityTaken; - uint256 totalStableDebt; - uint256 totalVariableDebt; - uint256 averageStableBorrowRate; - uint256 reserveFactor; - address reserve; - address aToken; - } - - struct InitReserveParams { - address asset; - address aTokenAddress; - address stableDebtAddress; - address variableDebtAddress; - address interestRateStrategyAddress; - uint16 reservesCount; - uint16 maxNumberReserves; - } - -} - -/** - * @title IPoolAddressesProvider - * @author Aave - * @notice Defines the basic interface for a Pool Addresses Provider. - */ -interface IPoolAddressesProvider { - - /** - * @dev Emitted when the market identifier is updated. - * @param oldMarketId The old id of the market - * @param newMarketId The new id of the market - */ - event MarketIdSet(string indexed oldMarketId, string indexed newMarketId); - - /** - * @dev Emitted when the pool is updated. - * @param oldAddress The old address of the Pool - * @param newAddress The new address of the Pool - */ - event PoolUpdated(address indexed oldAddress, address indexed newAddress); - - /** - * @dev Emitted when the pool configurator is updated. - * @param oldAddress The old address of the PoolConfigurator - * @param newAddress The new address of the PoolConfigurator - */ - event PoolConfiguratorUpdated( - address indexed oldAddress, address indexed newAddress - ); - - /** - * @dev Emitted when the price oracle is updated. - * @param oldAddress The old address of the PriceOracle - * @param newAddress The new address of the PriceOracle - */ - event PriceOracleUpdated( - address indexed oldAddress, address indexed newAddress - ); - - /** - * @dev Emitted when the ACL manager is updated. - * @param oldAddress The old address of the ACLManager - * @param newAddress The new address of the ACLManager - */ - event ACLManagerUpdated( - address indexed oldAddress, address indexed newAddress - ); - - /** - * @dev Emitted when the ACL admin is updated. - * @param oldAddress The old address of the ACLAdmin - * @param newAddress The new address of the ACLAdmin - */ - event ACLAdminUpdated( - address indexed oldAddress, address indexed newAddress - ); - - /** - * @dev Emitted when the price oracle sentinel is updated. - * @param oldAddress The old address of the PriceOracleSentinel - * @param newAddress The new address of the PriceOracleSentinel - */ - event PriceOracleSentinelUpdated( - address indexed oldAddress, address indexed newAddress - ); - - /** - * @dev Emitted when the pool data provider is updated. - * @param oldAddress The old address of the PoolDataProvider - * @param newAddress The new address of the PoolDataProvider - */ - event PoolDataProviderUpdated( - address indexed oldAddress, address indexed newAddress - ); - - /** - * @dev Emitted when a new proxy is created. - * @param id The identifier of the proxy - * @param proxyAddress The address of the created proxy contract - * @param implementationAddress The address of the implementation contract - */ - event ProxyCreated( - bytes32 indexed id, - address indexed proxyAddress, - address indexed implementationAddress - ); - - /** - * @dev Emitted when a new non-proxied contract address is registered. - * @param id The identifier of the contract - * @param oldAddress The address of the old contract - * @param newAddress The address of the new contract - */ - event AddressSet( - bytes32 indexed id, - address indexed oldAddress, - address indexed newAddress - ); - - /** - * @dev Emitted when the implementation of the proxy registered with id is - * updated - * @param id The identifier of the contract - * @param proxyAddress The address of the proxy contract - * @param oldImplementationAddress The address of the old implementation - * contract - * @param newImplementationAddress The address of the new implementation - * contract - */ - event AddressSetAsProxy( - bytes32 indexed id, - address indexed proxyAddress, - address oldImplementationAddress, - address indexed newImplementationAddress - ); - - /** - * @notice Returns the id of the Aave market to which this contract points - * to. - * @return The market id - */ - function getMarketId() external view returns (string memory); - - /** - * @notice Associates an id with a specific PoolAddressesProvider. - * @dev This can be used to create an onchain registry of - * PoolAddressesProviders to - * identify and validate multiple Aave markets. - * @param newMarketId The market id - */ - function setMarketId(string calldata newMarketId) external; - - /** - * @notice Returns an address by its identifier. - * @dev The returned address might be an EOA or a contract, potentially - * proxied - * @dev It returns ZERO if there is no registered address with the given id - * @param id The id - * @return The address of the registered for the specified id - */ - function getAddress(bytes32 id) external view returns (address); - - /** - * @notice General function to update the implementation of a proxy - * registered with - * certain `id`. If there is no proxy registered, it will instantiate one - * and - * set as implementation the `newImplementationAddress`. - * @dev IMPORTANT Use this function carefully, only for ids that don't have - * an explicit - * setter function, in order to avoid unexpected consequences - * @param id The id - * @param newImplementationAddress The address of the new implementation - */ - function setAddressAsProxy( - bytes32 id, - address newImplementationAddress - ) - external; - - /** - * @notice Sets an address for an id replacing the address saved in the - * addresses map. - * @dev IMPORTANT Use this function carefully, as it will do a hard - * replacement - * @param id The id - * @param newAddress The address to set - */ - function setAddress(bytes32 id, address newAddress) external; - - /** - * @notice Returns the address of the Pool proxy. - * @return The Pool proxy address - */ - function getPool() external view returns (address); - - /** - * @notice Updates the implementation of the Pool, or creates a proxy - * setting the new `pool` implementation when the function is called for the - * first time. - * @param newPoolImpl The new Pool implementation - */ - function setPoolImpl(address newPoolImpl) external; - - /** - * @notice Returns the address of the PoolConfigurator proxy. - * @return The PoolConfigurator proxy address - */ - function getPoolConfigurator() external view returns (address); - - /** - * @notice Updates the implementation of the PoolConfigurator, or creates a - * proxy - * setting the new `PoolConfigurator` implementation when the function is - * called for the first time. - * @param newPoolConfiguratorImpl The new PoolConfigurator implementation - */ - function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) - external; - - /** - * @notice Returns the address of the price oracle. - * @return The address of the PriceOracle - */ - function getPriceOracle() external view returns (address); - - /** - * @notice Updates the address of the price oracle. - * @param newPriceOracle The address of the new PriceOracle - */ - function setPriceOracle(address newPriceOracle) external; - - /** - * @notice Returns the address of the ACL manager. - * @return The address of the ACLManager - */ - function getACLManager() external view returns (address); - - /** - * @notice Updates the address of the ACL manager. - * @param newAclManager The address of the new ACLManager - */ - function setACLManager(address newAclManager) external; - - /** - * @notice Returns the address of the ACL admin. - * @return The address of the ACL admin - */ - function getACLAdmin() external view returns (address); - - /** - * @notice Updates the address of the ACL admin. - * @param newAclAdmin The address of the new ACL admin - */ - function setACLAdmin(address newAclAdmin) external; - - /** - * @notice Returns the address of the price oracle sentinel. - * @return The address of the PriceOracleSentinel - */ - function getPriceOracleSentinel() external view returns (address); - - /** - * @notice Updates the address of the price oracle sentinel. - * @param newPriceOracleSentinel The address of the new PriceOracleSentinel - */ - function setPriceOracleSentinel(address newPriceOracleSentinel) external; - - /** - * @notice Returns the address of the data provider. - * @return The address of the DataProvider - */ - function getPoolDataProvider() external view returns (address); - - /** - * @notice Updates the address of the data provider. - * @param newDataProvider The address of the new DataProvider - */ - function setPoolDataProvider(address newDataProvider) external; - -} - -/** - * @title IPool - * @author Aave - * @notice Defines the basic interface for an Aave Pool. - */ interface IPool { - /** - * @dev Emitted on mintUnbacked() - * @param reserve The address of the underlying asset of the reserve - * @param user The address initiating the supply - * @param onBehalfOf The beneficiary of the supplied assets, receiving the - * aTokens - * @param amount The amount of supplied assets - * @param referralCode The referral code used - */ - event MintUnbacked( - address indexed reserve, - address user, - address indexed onBehalfOf, - uint256 amount, - uint16 indexed referralCode - ); - - /** - * @dev Emitted on backUnbacked() - * @param reserve The address of the underlying asset of the reserve - * @param backer The address paying for the backing - * @param amount The amount added as backing - * @param fee The amount paid in fees - */ - event BackUnbacked( - address indexed reserve, - address indexed backer, - uint256 amount, - uint256 fee - ); - - /** - * @dev Emitted on supply() - * @param reserve The address of the underlying asset of the reserve - * @param user The address initiating the supply - * @param onBehalfOf The beneficiary of the supply, receiving the aTokens - * @param amount The amount supplied - * @param referralCode The referral code used - */ - event Supply( - address indexed reserve, - address user, - address indexed onBehalfOf, - uint256 amount, - uint16 indexed referralCode - ); - - /** - * @dev Emitted on withdraw() - * @param reserve The address of the underlying asset being withdrawn - * @param user The address initiating the withdrawal, owner of aTokens - * @param to The address that will receive the underlying - * @param amount The amount to be withdrawn - */ - event Withdraw( - address indexed reserve, - address indexed user, - address indexed to, - uint256 amount - ); - - /** - * @dev Emitted on borrow() and flashLoan() when debt needs to be opened - * @param reserve The address of the underlying asset being borrowed - * @param user The address of the user initiating the borrow(), receiving - * the funds on borrow() or just - * initiator of the transaction on flashLoan() - * @param onBehalfOf The address that will be getting the debt - * @param amount The amount borrowed out - * @param interestRateMode The rate mode: 1 for Stable, 2 for Variable - * @param borrowRate The numeric rate at which the user has borrowed, - * expressed in ray - * @param referralCode The referral code used - */ - event Borrow( - address indexed reserve, - address user, - address indexed onBehalfOf, - uint256 amount, - DataTypes.InterestRateMode interestRateMode, - uint256 borrowRate, - uint16 indexed referralCode - ); - - /** - * @dev Emitted on repay() - * @param reserve The address of the underlying asset of the reserve - * @param user The beneficiary of the repayment, getting his debt reduced - * @param repayer The address of the user initiating the repay(), providing - * the funds - * @param amount The amount repaid - * @param useATokens True if the repayment is done using aTokens, `false` if - * done with underlying asset directly - */ - event Repay( - address indexed reserve, - address indexed user, - address indexed repayer, - uint256 amount, - bool useATokens - ); - - /** - * @dev Emitted on swapBorrowRateMode() - * @param reserve The address of the underlying asset of the reserve - * @param user The address of the user swapping his rate mode - * @param interestRateMode The current interest rate mode of the position - * being swapped: 1 for Stable, 2 for Variable - */ - event SwapBorrowRateMode( - address indexed reserve, - address indexed user, - DataTypes.InterestRateMode interestRateMode - ); - - /** - * @dev Emitted on borrow(), repay() and liquidationCall() when using - * isolated assets - * @param asset The address of the underlying asset of the reserve - * @param totalDebt The total isolation mode debt for the reserve - */ - event IsolationModeTotalDebtUpdated( - address indexed asset, uint256 totalDebt - ); - - /** - * @dev Emitted when the user selects a certain asset category for eMode - * @param user The address of the user - * @param categoryId The category id - */ - event UserEModeSet(address indexed user, uint8 categoryId); - - /** - * @dev Emitted on setUserUseReserveAsCollateral() - * @param reserve The address of the underlying asset of the reserve - * @param user The address of the user enabling the usage as collateral - */ - event ReserveUsedAsCollateralEnabled( - address indexed reserve, address indexed user - ); - - /** - * @dev Emitted on setUserUseReserveAsCollateral() - * @param reserve The address of the underlying asset of the reserve - * @param user The address of the user enabling the usage as collateral - */ - event ReserveUsedAsCollateralDisabled( - address indexed reserve, address indexed user - ); - - /** - * @dev Emitted on rebalanceStableBorrowRate() - * @param reserve The address of the underlying asset of the reserve - * @param user The address of the user for which the rebalance has been - * executed - */ - event RebalanceStableBorrowRate( - address indexed reserve, address indexed user - ); - - /** - * @dev Emitted on flashLoan() - * @param target The address of the flash loan receiver contract - * @param initiator The address initiating the flash loan - * @param asset The address of the asset being flash borrowed - * @param amount The amount flash borrowed - * @param interestRateMode The flashloan mode: 0 for regular flashloan, 1 - * for Stable debt, 2 for Variable debt - * @param premium The fee flash borrowed - * @param referralCode The referral code used - */ - event FlashLoan( - address indexed target, - address initiator, - address indexed asset, - uint256 amount, - DataTypes.InterestRateMode interestRateMode, - uint256 premium, - uint16 indexed referralCode - ); - - /** - * @dev Emitted when a borrower is liquidated. - * @param collateralAsset The address of the underlying asset used as - * collateral, to receive as result of the liquidation - * @param debtAsset The address of the underlying borrowed asset to be - * repaid with the liquidation - * @param user The address of the borrower getting liquidated - * @param debtToCover The debt amount of borrowed `asset` the liquidator - * wants to cover - * @param liquidatedCollateralAmount The amount of collateral received by - * the liquidator - * @param liquidator The address of the liquidator - * @param receiveAToken True if the liquidators wants to receive the - * collateral aTokens, `false` if he wants - * to receive the underlying collateral asset directly - */ - event LiquidationCall( - address indexed collateralAsset, - address indexed debtAsset, - address indexed user, - uint256 debtToCover, - uint256 liquidatedCollateralAmount, - address liquidator, - bool receiveAToken - ); - - /** - * @dev Emitted when the state of a reserve is updated. - * @param reserve The address of the underlying asset of the reserve - * @param liquidityRate The next liquidity rate - * @param stableBorrowRate The next stable borrow rate - * @param variableBorrowRate The next variable borrow rate - * @param liquidityIndex The next liquidity index - * @param variableBorrowIndex The next variable borrow index - */ - event ReserveDataUpdated( - address indexed reserve, - uint256 liquidityRate, - uint256 stableBorrowRate, - uint256 variableBorrowRate, - uint256 liquidityIndex, - uint256 variableBorrowIndex - ); - - /** - * @dev Emitted when the protocol treasury receives minted aTokens from the - * accrued interest. - * @param reserve The address of the reserve - * @param amountMinted The amount minted to the treasury - */ - event MintedToTreasury(address indexed reserve, uint256 amountMinted); - - /** - * @notice Mints an `amount` of aTokens to the `onBehalfOf` - * @param asset The address of the underlying asset to mint - * @param amount The amount to mint - * @param onBehalfOf The address that will receive the aTokens - * @param referralCode Code used to register the integrator originating the - * operation, for potential rewards. - * 0 if the action is executed directly by the user, without any - * middle-man - */ - function mintUnbacked( - address asset, - uint256 amount, - address onBehalfOf, - uint16 referralCode - ) - external; - - /** - * @notice Back the current unbacked underlying with `amount` and pay `fee`. - * @param asset The address of the underlying asset to back - * @param amount The amount to back - * @param fee The amount paid in fees - * @return The backed amount - */ - function backUnbacked( - address asset, - uint256 amount, - uint256 fee - ) - external - returns (uint256); - - /** - * @notice Supplies an `amount` of underlying asset into the reserve, - * receiving in return overlying aTokens. - * - E.g. User supplies 100 USDC and gets in return 100 aUSDC - * @param asset The address of the underlying asset to supply - * @param amount The amount to be supplied - * @param onBehalfOf The address that will receive the aTokens, same as - * msg.sender if the user - * wants to receive them on his own wallet, or a different address if the - * beneficiary of aTokens - * is a different wallet - * @param referralCode Code used to register the integrator originating the - * operation, for potential rewards. - * 0 if the action is executed directly by the user, without any - * middle-man - */ - function supply( - address asset, - uint256 amount, - address onBehalfOf, - uint16 referralCode - ) - external; - - /** - * @notice Supply with transfer approval of asset to be supplied done via - * permit function - * see: https://eips.ethereum.org/EIPS/eip-2612 and - * https://eips.ethereum.org/EIPS/eip-713 - * @param asset The address of the underlying asset to supply - * @param amount The amount to be supplied - * @param onBehalfOf The address that will receive the aTokens, same as - * msg.sender if the user - * wants to receive them on his own wallet, or a different address if the - * beneficiary of aTokens - * is a different wallet - * @param deadline The deadline timestamp that the permit is valid - * @param referralCode Code used to register the integrator originating the - * operation, for potential rewards. - * 0 if the action is executed directly by the user, without any - * middle-man - * @param permitV The V parameter of ERC712 permit sig - * @param permitR The R parameter of ERC712 permit sig - * @param permitS The S parameter of ERC712 permit sig - */ - function supplyWithPermit( - address asset, - uint256 amount, - address onBehalfOf, - uint16 referralCode, - uint256 deadline, - uint8 permitV, - bytes32 permitR, - bytes32 permitS - ) - external; - - /** - * @notice Withdraws an `amount` of underlying asset from the reserve, - * burning the equivalent aTokens owned - * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning - * the 100 aUSDC - * @param asset The address of the underlying asset to withdraw - * @param amount The underlying amount to be withdrawn - * - Send the value type(uint256).max in order to withdraw the whole - * aToken balance - * @param to The address that will receive the underlying, same as - * msg.sender if the user - * wants to receive it on his own wallet, or a different address if the - * beneficiary is a - * different wallet - * @return The final amount withdrawn - */ - function withdraw( - address asset, - uint256 amount, - address to - ) - external - returns (uint256); - - /** - * @notice Allows users to borrow a specific `amount` of the reserve - * underlying asset, provided that the borrower - * already supplied enough collateral, or he was given enough allowance by a - * credit delegator on the - * corresponding debt token (StableDebtToken or VariableDebtToken) - * - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, - * receiving the 100 USDC in his wallet - * and 100 stable/variable debt tokens, depending on the - * `interestRateMode` - * @param asset The address of the underlying asset to borrow - * @param amount The amount to be borrowed - * @param interestRateMode The interest rate mode at which the user wants to - * borrow: 1 for Stable, 2 for Variable - * @param referralCode The code used to register the integrator originating - * the operation, for potential rewards. - * 0 if the action is executed directly by the user, without any - * middle-man - * @param onBehalfOf The address of the user who will receive the debt. - * Should be the address of the borrower itself - * calling the function if he wants to borrow against his own collateral, or - * the address of the credit delegator - * if he has been given credit delegation allowance - */ - function borrow( - address asset, - uint256 amount, - uint256 interestRateMode, - uint16 referralCode, - address onBehalfOf - ) - external; - - /** - * @notice Repays a borrowed `amount` on a specific reserve, burning the - * equivalent debt tokens owned - * - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of - * the `onBehalfOf` address - * @param asset The address of the borrowed underlying asset previously - * borrowed - * @param amount The amount to repay - * - Send the value type(uint256).max in order to repay the whole debt for - * `asset` on the specific `debtMode` - * @param interestRateMode The interest rate mode at of the debt the user - * wants to repay: 1 for Stable, 2 for Variable - * @param onBehalfOf The address of the user who will get his debt - * reduced/removed. Should be the address of the - * user calling the function if he wants to reduce/remove his own debt, or - * the address of any other - * other borrower whose debt should be removed - * @return The final amount repaid - */ - function repay( - address asset, - uint256 amount, - uint256 interestRateMode, - address onBehalfOf - ) - external - returns (uint256); - - /** - * @notice Repay with transfer approval of asset to be repaid done via - * permit function - * see: https://eips.ethereum.org/EIPS/eip-2612 and - * https://eips.ethereum.org/EIPS/eip-713 - * @param asset The address of the borrowed underlying asset previously - * borrowed - * @param amount The amount to repay - * - Send the value type(uint256).max in order to repay the whole debt for - * `asset` on the specific `debtMode` - * @param interestRateMode The interest rate mode at of the debt the user - * wants to repay: 1 for Stable, 2 for Variable - * @param onBehalfOf Address of the user who will get his debt - * reduced/removed. Should be the address of the - * user calling the function if he wants to reduce/remove his own debt, or - * the address of any other - * other borrower whose debt should be removed - * @param deadline The deadline timestamp that the permit is valid - * @param permitV The V parameter of ERC712 permit sig - * @param permitR The R parameter of ERC712 permit sig - * @param permitS The S parameter of ERC712 permit sig - * @return The final amount repaid - */ - function repayWithPermit( - address asset, - uint256 amount, - uint256 interestRateMode, - address onBehalfOf, - uint256 deadline, - uint8 permitV, - bytes32 permitR, - bytes32 permitS - ) - external - returns (uint256); - - /** - * @notice Repays a borrowed `amount` on a specific reserve using the - * reserve aTokens, burning the - * equivalent debt tokens - * - E.g. User repays 100 USDC using 100 aUSDC, burning 100 variable/stable - * debt tokens - * @dev Passing uint256.max as amount will clean up any residual aToken - * dust balance, if the user aToken - * balance is not enough to cover the whole debt - * @param asset The address of the borrowed underlying asset previously - * borrowed - * @param amount The amount to repay - * - Send the value type(uint256).max in order to repay the whole debt for - * `asset` on the specific `debtMode` - * @param interestRateMode The interest rate mode at of the debt the user - * wants to repay: 1 for Stable, 2 for Variable - * @return The final amount repaid - */ - function repayWithATokens( - address asset, - uint256 amount, - uint256 interestRateMode - ) - external - returns (uint256); - - /** - * @notice Allows a borrower to swap his debt between stable and variable - * mode, or vice versa - * @param asset The address of the underlying asset borrowed - * @param interestRateMode The current interest rate mode of the position - * being swapped: 1 for Stable, 2 for Variable - */ - function swapBorrowRateMode( - address asset, - uint256 interestRateMode - ) - external; - - /** - * @notice Rebalances the stable interest rate of a user to the current - * stable rate defined on the reserve. - * - Users can be rebalanced if the following conditions are satisfied: - * 1. Usage ratio is above 95% - * 2. the current supply APY is below REBALANCE_UP_THRESHOLD * - * maxVariableBorrowRate, which means that too - * much has been borrowed at a stable rate and suppliers are not - * earning enough - * @param asset The address of the underlying asset borrowed - * @param user The address of the user to be rebalanced - */ - function rebalanceStableBorrowRate(address asset, address user) external; - - /** - * @notice Allows suppliers to enable/disable a specific supplied asset as - * collateral - * @param asset The address of the underlying asset supplied - * @param useAsCollateral True if the user wants to use the supply as - * collateral, false otherwise - */ - function setUserUseReserveAsCollateral( - address asset, - bool useAsCollateral - ) - external; - - /** - * @notice Function to liquidate a non-healthy position collateral-wise, - * with Health Factor below 1 - * - The caller (liquidator) covers `debtToCover` amount of debt of the user - * getting liquidated, and receives - * a proportionally amount of the `collateralAsset` plus a bonus to cover - * market risk - * @param collateralAsset The address of the underlying asset used as - * collateral, to receive as result of the liquidation - * @param debtAsset The address of the underlying borrowed asset to be - * repaid with the liquidation - * @param user The address of the borrower getting liquidated - * @param debtToCover The debt amount of borrowed `asset` the liquidator - * wants to cover - * @param receiveAToken True if the liquidators wants to receive the - * collateral aTokens, `false` if he wants - * to receive the underlying collateral asset directly - */ - function liquidationCall( - address collateralAsset, - address debtAsset, - address user, - uint256 debtToCover, - bool receiveAToken - ) - external; - - /** - * @notice Allows smartcontracts to access the liquidity of the pool within - * one transaction, - * as long as the amount taken plus a fee is returned. - * @dev IMPORTANT There are security concerns for developers of flashloan - * receiver contracts that must be kept - * into consideration. For further details please visit - * https://docs.aave.com/developers/ - * @param receiverAddress The address of the contract receiving the funds, - * implementing IFlashLoanReceiver interface - * @param assets The addresses of the assets being flash-borrowed - * @param amounts The amounts of the assets being flash-borrowed - * @param interestRateModes Types of the debt to open if the flash loan is - * not returned: - * 0 -> Don't open any debt, just revert if funds can't be transferred - * from the receiver - * 1 -> Open debt at stable rate for the value of the amount - * flash-borrowed to the `onBehalfOf` address - * 2 -> Open debt at variable rate for the value of the amount - * flash-borrowed to the `onBehalfOf` address - * @param onBehalfOf The address that will receive the debt in the case of - * using on `modes` 1 or 2 - * @param params Variadic packed params to pass to the receiver as extra - * information - * @param referralCode The code used to register the integrator originating - * the operation, for potential rewards. - * 0 if the action is executed directly by the user, without any - * middle-man - */ - function flashLoan( - address receiverAddress, - address[] calldata assets, - uint256[] calldata amounts, - uint256[] calldata interestRateModes, - address onBehalfOf, - bytes calldata params, - uint16 referralCode - ) - external; - - /** - * @notice Allows smartcontracts to access the liquidity of the pool within - * one transaction, - * as long as the amount taken plus a fee is returned. - * @dev IMPORTANT There are security concerns for developers of flashloan - * receiver contracts that must be kept - * into consideration. For further details please visit - * https://docs.aave.com/developers/ - * @param receiverAddress The address of the contract receiving the funds, - * implementing IFlashLoanSimpleReceiver interface - * @param asset The address of the asset being flash-borrowed - * @param amount The amount of the asset being flash-borrowed - * @param params Variadic packed params to pass to the receiver as extra - * information - * @param referralCode The code used to register the integrator originating - * the operation, for potential rewards. - * 0 if the action is executed directly by the user, without any - * middle-man - */ function flashLoanSimple( address receiverAddress, address asset, @@ -1150,385 +12,4 @@ interface IPool { ) external; - /** - * @notice Returns the user account data across all the reserves - * @param user The address of the user - * @return totalCollateralBase The total collateral of the user in the base - * currency used by the price feed - * @return totalDebtBase The total debt of the user in the base currency - * used by the price feed - * @return availableBorrowsBase The borrowing power left of the user in the - * base currency used by the price feed - * @return currentLiquidationThreshold The liquidation threshold of the user - * @return ltv The loan to value of The user - * @return healthFactor The current health factor of the user - */ - function getUserAccountData(address user) - external - view - returns ( - uint256 totalCollateralBase, - uint256 totalDebtBase, - uint256 availableBorrowsBase, - uint256 currentLiquidationThreshold, - uint256 ltv, - uint256 healthFactor - ); - - /** - * @notice Initializes a reserve, activating it, assigning an aToken and - * debt tokens and an - * interest rate strategy - * @dev Only callable by the PoolConfigurator contract - * @param asset The address of the underlying asset of the reserve - * @param aTokenAddress The address of the aToken that will be assigned to - * the reserve - * @param stableDebtAddress The address of the StableDebtToken that will be - * assigned to the reserve - * @param variableDebtAddress The address of the VariableDebtToken that will - * be assigned to the reserve - * @param interestRateStrategyAddress The address of the interest rate - * strategy contract - */ - function initReserve( - address asset, - address aTokenAddress, - address stableDebtAddress, - address variableDebtAddress, - address interestRateStrategyAddress - ) - external; - - /** - * @notice Drop a reserve - * @dev Only callable by the PoolConfigurator contract - * @param asset The address of the underlying asset of the reserve - */ - function dropReserve(address asset) external; - - /** - * @notice Updates the address of the interest rate strategy contract - * @dev Only callable by the PoolConfigurator contract - * @param asset The address of the underlying asset of the reserve - * @param rateStrategyAddress The address of the interest rate strategy - * contract - */ - function setReserveInterestRateStrategyAddress( - address asset, - address rateStrategyAddress - ) - external; - - /** - * @notice Sets the configuration bitmap of the reserve as a whole - * @dev Only callable by the PoolConfigurator contract - * @param asset The address of the underlying asset of the reserve - * @param configuration The new configuration bitmap - */ - function setConfiguration( - address asset, - DataTypes.ReserveConfigurationMap calldata configuration - ) - external; - - /** - * @notice Returns the configuration of the reserve - * @param asset The address of the underlying asset of the reserve - * @return The configuration of the reserve - */ - function getConfiguration(address asset) - external - view - returns (DataTypes.ReserveConfigurationMap memory); - - /** - * @notice Returns the configuration of the user across all the reserves - * @param user The user address - * @return The configuration of the user - */ - function getUserConfiguration(address user) - external - view - returns (DataTypes.UserConfigurationMap memory); - - /** - * @notice Returns the normalized income of the reserve - * @param asset The address of the underlying asset of the reserve - * @return The reserve's normalized income - */ - function getReserveNormalizedIncome(address asset) - external - view - returns (uint256); - - /** - * @notice Returns the normalized variable debt per unit of asset - * @dev WARNING: This function is intended to be used primarily by the - * protocol itself to get a - * "dynamic" variable index based on time, current stored index and virtual - * rate at the current - * moment (approx. a borrower would get if opening a position). This means - * that is always used in - * combination with variable debt supply/balances. - * If using this function externally, consider that is possible to have an - * increasing normalized - * variable debt that is not equivalent to how the variable debt index would - * be updated in storage - * (e.g. only updates with non-zero variable debt supply) - * @param asset The address of the underlying asset of the reserve - * @return The reserve normalized variable debt - */ - function getReserveNormalizedVariableDebt(address asset) - external - view - returns (uint256); - - /** - * @notice Returns the state and configuration of the reserve - * @param asset The address of the underlying asset of the reserve - * @return The state and configuration data of the reserve - */ - function getReserveData(address asset) - external - view - returns (DataTypes.ReserveData memory); - - /** - * @notice Validates and finalizes an aToken transfer - * @dev Only callable by the overlying aToken of the `asset` - * @param asset The address of the underlying asset of the aToken - * @param from The user from which the aTokens are transferred - * @param to The user receiving the aTokens - * @param amount The amount being transferred/withdrawn - * @param balanceFromBefore The aToken balance of the `from` user before the - * transfer - * @param balanceToBefore The aToken balance of the `to` user before the - * transfer - */ - function finalizeTransfer( - address asset, - address from, - address to, - uint256 amount, - uint256 balanceFromBefore, - uint256 balanceToBefore - ) - external; - - /** - * @notice Returns the list of the underlying assets of all the initialized - * reserves - * @dev It does not include dropped reserves - * @return The addresses of the underlying assets of the initialized - * reserves - */ - function getReservesList() external view returns (address[] memory); - - /** - * @notice Returns the address of the underlying asset of a reserve by the - * reserve id as stored in the DataTypes.ReserveData struct - * @param id The id of the reserve as stored in the DataTypes.ReserveData - * struct - * @return The address of the reserve associated with id - */ - function getReserveAddressById(uint16 id) external view returns (address); - - /** - * @notice Returns the PoolAddressesProvider connected to this contract - * @return The address of the PoolAddressesProvider - */ - function ADDRESSES_PROVIDER() - external - view - returns (IPoolAddressesProvider); - - /** - * @notice Updates the protocol fee on the bridging - * @param bridgeProtocolFee The part of the premium sent to the protocol - * treasury - */ - function updateBridgeProtocolFee(uint256 bridgeProtocolFee) external; - - /** - * @notice Updates flash loan premiums. Flash loan premium consists of two - * parts: - * - A part is sent to aToken holders as extra, one time accumulated - * interest - * - A part is collected by the protocol treasury - * @dev The total premium is calculated on the total borrowed amount - * @dev The premium to protocol is calculated on the total premium, being a - * percentage of `flashLoanPremiumTotal` - * @dev Only callable by the PoolConfigurator contract - * @param flashLoanPremiumTotal The total premium, expressed in bps - * @param flashLoanPremiumToProtocol The part of the premium sent to the - * protocol treasury, expressed in bps - */ - function updateFlashloanPremiums( - uint128 flashLoanPremiumTotal, - uint128 flashLoanPremiumToProtocol - ) - external; - - /** - * @notice Configures a new category for the eMode. - * @dev In eMode, the protocol allows very high borrowing power to borrow - * assets of the same category. - * The category 0 is reserved as it's the default for volatile assets - * @param id The id of the category - * @param config The configuration of the category - */ - function configureEModeCategory( - uint8 id, - DataTypes.EModeCategory memory config - ) - external; - - /** - * @notice Returns the data of an eMode category - * @param id The id of the category - * @return The configuration data of the category - */ - function getEModeCategoryData(uint8 id) - external - view - returns (DataTypes.EModeCategory memory); - - /** - * @notice Allows a user to use the protocol in eMode - * @param categoryId The id of the category - */ - function setUserEMode(uint8 categoryId) external; - - /** - * @notice Returns the eMode the user is using - * @param user The address of the user - * @return The eMode id - */ - function getUserEMode(address user) external view returns (uint256); - - /** - * @notice Resets the isolation mode total debt of the given asset to zero - * @dev It requires the given asset has zero debt ceiling - * @param asset The address of the underlying asset to reset the - * isolationModeTotalDebt - */ - function resetIsolationModeTotalDebt(address asset) external; - - /** - * @notice Returns the percentage of available liquidity that can be - * borrowed at once at stable rate - * @return The percentage of available liquidity to borrow, expressed in bps - */ - function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() - external - view - returns (uint256); - - /** - * @notice Returns the total fee on flash loans - * @return The total fee on flashloans - */ - function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128); - - /** - * @notice Returns the part of the bridge fees sent to protocol - * @return The bridge fee sent to the protocol treasury - */ - function BRIDGE_PROTOCOL_FEE() external view returns (uint256); - - /** - * @notice Returns the part of the flashloan fees sent to protocol - * @return The flashloan fee sent to the protocol treasury - */ - function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint128); - - /** - * @notice Returns the maximum number of reserves supported to be listed in - * this Pool - * @return The maximum number of reserves supported - */ - function MAX_NUMBER_RESERVES() external view returns (uint16); - - /** - * @notice Mints the assets accrued through the reserve factor to the - * treasury in the form of aTokens - * @param assets The list of reserves for which the minting needs to be - * executed - */ - function mintToTreasury(address[] calldata assets) external; - - /** - * @notice Rescue and transfer tokens locked in this contract - * @param token The address of the token - * @param to The address of the recipient - * @param amount The amount of token to transfer - */ - function rescueTokens(address token, address to, uint256 amount) external; - - /** - * @notice Supplies an `amount` of underlying asset into the reserve, - * receiving in return overlying aTokens. - * - E.g. User supplies 100 USDC and gets in return 100 aUSDC - * @dev Deprecated: Use the `supply` function instead - * @param asset The address of the underlying asset to supply - * @param amount The amount to be supplied - * @param onBehalfOf The address that will receive the aTokens, same as - * msg.sender if the user - * wants to receive them on his own wallet, or a different address if the - * beneficiary of aTokens - * is a different wallet - * @param referralCode Code used to register the integrator originating the - * operation, for potential rewards. - * 0 if the action is executed directly by the user, without any - * middle-man - */ - function deposit( - address asset, - uint256 amount, - address onBehalfOf, - uint16 referralCode - ) - external; - -} - -/** - * @title IFlashLoanSimpleReceiver - * @author Aave - * @notice Defines the basic interface of a flashloan-receiver contract. - * @dev Implement this interface to develop a flashloan-compatible - * flashLoanReceiver contract - */ -interface IFlashLoanSimpleReceiver { - - /** - * @notice Executes an operation after receiving the flash-borrowed asset - * @dev Ensure that the contract can return the debt + premium, e.g., has - * enough funds to repay and has approved the Pool to pull the total - * amount - * @param asset The address of the flash-borrowed asset - * @param amount The amount of the flash-borrowed asset - * @param premium The fee of the flash-borrowed asset - * @param initiator The address of the flashloan initiator - * @param params The byte-encoded params passed when initiating the - * flashloan - * @return True if the execution of the operation succeeds, false otherwise - */ - function executeOperation( - address asset, - uint256 amount, - uint256 premium, - address initiator, - bytes calldata params - ) - external - returns (bool); - - function ADDRESSES_PROVIDER() - external - view - returns (IPoolAddressesProvider); - - function POOL() external view returns (IPool); - } diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index cc89afe..7b01ebf 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -1,41 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -/// @title Reduced Interface of the ERC20 standard as defined in the EIP -/// @author OpenZeppelin interface IERC20 { - /// @dev Returns the number of decimals used to get its user representation. - /// For example, if `decimals` equals `2`, a balance of `505` tokens should - /// be displayed to a user as `5.05` (`505 / 10 ** 2`). function decimals() external view returns (uint8); - /// @dev Returns the amount of tokens owned by `account`. function balanceOf(address account) external view returns (uint256); - /// @dev Moves `amount` tokens from the caller's account to `to` - /// @param to The address of the recipient - /// @param amount The amount of tokens to transfer - /// @return a boolean value indicating whether the operation succeeded - /// Emits a {Transfer} event function transfer(address to, uint256 amount) external returns (bool); - /// @dev Sets `amount` as the allowance of `spender` over the caller's - /// tokens - /// @param spender The address of the account to grant the allowance - /// @param amount The amount of tokens to allow - /// @return a boolean value indicating whether the operation succeeded - /// Emits an {Approval} event. function approve(address spender, uint256 amount) external returns (bool); - /// @dev Moves `amount` tokens from `from` to `to` using the - /// allowance mechanism. `amount` is then deducted from the caller's - /// allowance - /// @param from The address of the sender - /// @param to The address of the recipient - /// @param amount The amount of tokens to transfer - /// @return a boolean value indicating whether the operation succeeded. - /// Emits a {Transfer} event function transferFrom( address from, address to, diff --git a/src/interfaces/ISynthetix.sol b/src/interfaces/ISynthetix.sol index 7fd29f1..62d73a4 100644 --- a/src/interfaces/ISynthetix.sol +++ b/src/interfaces/ISynthetix.sol @@ -1,37 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -/// @custom:todo remove extraneous code - -/// @title Consolidated Spot Market Proxy Interface -/// @notice Responsible for interacting with Synthetix v3 spot markets -/// @author Synthetix interface ISpotMarket { - /*////////////////////////////////////////////////////////////// - MARKET INTERFACE - //////////////////////////////////////////////////////////////*/ - - /// @notice returns a human-readable name for a given market - function name(uint128 marketId) external view returns (string memory); - - /*////////////////////////////////////////////////////////////// - SPOT MARKET FACTORY MODULE - //////////////////////////////////////////////////////////////*/ - - /// @notice Get the proxy address of the synth for the provided marketId - /// @dev Uses associated systems module to retrieve the token address. - /// @param marketId id of the market - /// @return synthAddress address of the proxy for the synth - function getSynth(uint128 marketId) - external - view - returns (address synthAddress); - - /*////////////////////////////////////////////////////////////// - WRAPPER MODULE - //////////////////////////////////////////////////////////////*/ - struct Data { uint256 fixedFees; uint256 utilizationFees; @@ -39,30 +10,11 @@ interface ISpotMarket { int256 wrapperFees; } - struct OrderFeesData { - uint256 fixedFees; - uint256 utilizationFees; - int256 skewFees; - int256 wrapperFees; - } - - enum Tolerance { - DEFAULT, - STRICT - } + function getSynth(uint128 marketId) + external + view + returns (address synthAddress); - /// @notice Wraps the specified amount and returns similar value of synth - /// minus the fees. - /// @dev Fees are collected from the user by way of the contract returning - /// less synth than specified amount of collateral. - /// @param marketId Id of the market used for the trade. - /// @param wrapAmount Amount of collateral to wrap. This amount gets - /// deposited into the market collateral manager. - /// @param minAmountReceived The minimum amount of synths the trader is - /// expected to receive, otherwise the transaction will revert. - /// @return amountToMint Amount of synth returned to user. - /// @return fees breakdown of all fees. in this case, only wrapper fees are - /// returned. function wrap( uint128 marketId, uint256 wrapAmount, @@ -71,17 +23,6 @@ interface ISpotMarket { external returns (uint256 amountToMint, Data memory fees); - /// @notice Unwraps the synth and returns similar value of collateral minus - /// the fees. - /// @dev Transfers the specified synth, collects fees through configured fee - /// collector, returns collateral minus fees to trader. - /// @param marketId Id of the market used for the trade. - /// @param unwrapAmount Amount of synth trader is unwrapping. - /// @param minAmountReceived The minimum amount of collateral the trader is - /// expected to receive, otherwise the transaction will revert. - /// @return returnCollateralAmount Amount of collateral returned. - /// @return fees breakdown of all fees. in this case, only wrapper fees are - /// returned. function unwrap( uint128 marketId, uint256 unwrapAmount, @@ -90,26 +31,6 @@ interface ISpotMarket { external returns (uint256 returnCollateralAmount, Data memory fees); - /*////////////////////////////////////////////////////////////// - ATOMIC ORDER MODULE - //////////////////////////////////////////////////////////////*/ - - /// @notice Initiates a buy trade returning synth for the specified - /// amountUsd. - /// @dev Transfers the specified amountUsd, collects fees through configured - /// fee collector, returns synth to the trader. - /// @dev Leftover fees not collected get deposited into the market manager - /// to improve market PnL. - /// @dev Uses the buyFeedId configured for the market. - /// @param marketId Id of the market used for the trade. - /// @param usdAmount Amount of snxUSD trader is providing allowance for the - /// trade. - /// @param minAmountReceived Min Amount of synth is expected the trader to - /// receive otherwise the transaction will revert. - /// @param referrer Optional address of the referrer, for fee share - /// @return synthAmount Synth received on the trade based on amount provided - /// by trader. - /// @return fees breakdown of all the fees incurred for the transaction. function buy( uint128 marketId, uint256 usdAmount, @@ -119,20 +40,6 @@ interface ISpotMarket { external returns (uint256 synthAmount, Data memory fees); - /// @notice Initiates a sell trade returning snxUSD for the specified amount - /// of synth (sellAmount) - /// @dev Transfers the specified synth, collects fees through configured fee - /// collector, returns snxUSD to the trader. - /// @dev Leftover fees not collected get deposited into the market manager - /// to improve market PnL. - /// @param marketId Id of the market used for the trade. - /// @param synthAmount Amount of synth provided by trader for trade into - /// snxUSD. - /// @param minUsdAmount Min Amount of snxUSD trader expects to receive for - /// the trade - /// @param referrer Optional address of the referrer, for fee share - /// @return usdAmountReceived Amount of snxUSD returned to user - /// @return fees breakdown of all the fees incurred for the transaction. function sell( uint128 marketId, uint256 synthAmount, @@ -142,61 +49,10 @@ interface ISpotMarket { external returns (uint256 usdAmountReceived, Data memory fees); - /** - * @notice quote for buyExactIn. same parameters and return values as - * buyExactIn - * @param synthMarketId market id value - * @param usdAmount amount of USD to use for the trade - * @param stalenessTolerance this enum determines what staleness - * tolerance to use - * @return synthAmount return amount of synth given the USD amount - fees - * @return fees breakdown of all the quoted fees for the buy txn - */ - function quoteBuyExactIn( - uint128 synthMarketId, - uint256 usdAmount, - Tolerance stalenessTolerance - ) - external - view - returns (uint256 synthAmount, OrderFeesData memory fees); - -} - -interface IERC7412 { - - /// @dev Emitted when an oracle is requested to provide data. - /// Upon receipt of this error, a wallet client - /// should automatically resolve the requested oracle data - /// and call fulfillOracleQuery. - /// @param oracleContract The address of the oracle contract - /// (which is also the fulfillment contract). - /// @param oracleQuery The query to be sent to the off-chain interface. - error OracleDataRequired(address oracleContract, bytes oracleQuery); - - /// @dev Emitted when the recently posted oracle data requires - /// a fee to be paid. Upon receipt of this error, - /// a wallet client should attach the requested feeAmount - /// to the most recently posted oracle data transaction - error FeeRequired(uint256 feeAmount); - - /// @dev Upon resolving the oracle query, the client should - /// call this function to post the data to the - /// blockchain. - /// @param signedOffchainData The data that was returned - /// from the off-chain interface, signed by the oracle. - function fulfillOracleQuery(bytes calldata signedOffchainData) - external - payable; - } interface IPerpsMarket { - /// @notice modify the collateral delegated to the account - /// @param accountId id of the account - /// @param synthMarketId id of the synth market used as collateral - /// @param amountDelta requested change of collateral delegated function modifyCollateral( uint128 accountId, uint128 synthMarketId, @@ -204,30 +60,12 @@ interface IPerpsMarket { ) external; - function hasPermission( - uint128 accountId, - bytes32 permission, - address user - ) - external - view - returns (bool); - function renouncePermission( uint128 accountId, bytes32 permission ) external; - function createAccount() external returns (uint128 accountId); - - function grantPermission( - uint128 accountId, - bytes32 permission, - address user - ) - external; - function isAuthorized( uint128 accountId, bytes32 permission, @@ -237,999 +75,11 @@ interface IPerpsMarket { view returns (bool isAuthorized); - /** - * @notice Allows anyone to pay an account's debt - * @param accountId Id of the account. - * @param amount debt amount to pay off - */ function payDebt(uint128 accountId, uint256 amount) external; - /** - * @notice Returns account's debt - * @param accountId Id of the account. - * @return accountDebt specified account id's debt - */ function debt(uint128 accountId) external view returns (uint256 accountDebt); - /// @notice Returns the withdrawable margin given the discounted margin for - /// `accountId` accounting for any open position. Callers can invoke - /// `getMarginDigest` to retrieve the available margin (i.e. discounted - /// margin when there is a position open or marginUsd when there isn't). - /// @param accountId Account of the margin account to query against - /// @param marketId Market of the margin account to query against - /// @return getWithdrawableMargin Amount of margin in USD that can be - /// withdrawn - function getWithdrawableMargin( - uint128 accountId, - uint128 marketId - ) - external - view - returns (uint256); - -} - -interface ICore { - - error ImplementationIsSterile(address implementation); - error NoChange(); - error NotAContract(address contr); - error NotNominated(address addr); - error Unauthorized(address addr); - error UpgradeSimulationFailed(); - error ZeroAddress(); - - event OwnerChanged(address oldOwner, address newOwner); - event OwnerNominated(address newOwner); - event Upgraded(address indexed self, address implementation); - - function acceptOwnership() external; - - function getImplementation() external view returns (address); - - function nominateNewOwner(address newNominatedOwner) external; - - function nominatedOwner() external view returns (address); - - function owner() external view returns (address); - - function renounceNomination() external; - - function simulateUpgradeTo(address newImplementation) external; - - function upgradeTo(address newImplementation) external; - - error ValueAlreadyInSet(); - error ValueNotInSet(); - - event FeatureFlagAllowAllSet(bytes32 indexed feature, bool allowAll); - event FeatureFlagAllowlistAdded(bytes32 indexed feature, address account); - event FeatureFlagAllowlistRemoved(bytes32 indexed feature, address account); - event FeatureFlagDeniersReset(bytes32 indexed feature, address[] deniers); - event FeatureFlagDenyAllSet(bytes32 indexed feature, bool denyAll); - - function addToFeatureFlagAllowlist( - bytes32 feature, - address account - ) - external; - - function getDeniers(bytes32 feature) - external - view - returns (address[] memory); - - function getFeatureFlagAllowAll(bytes32 feature) - external - view - returns (bool); - - function getFeatureFlagAllowlist(bytes32 feature) - external - view - returns (address[] memory); - - function getFeatureFlagDenyAll(bytes32 feature) - external - view - returns (bool); - - function isFeatureAllowed( - bytes32 feature, - address account - ) - external - view - returns (bool); - - function removeFromFeatureFlagAllowlist( - bytes32 feature, - address account - ) - external; - - function setDeniers(bytes32 feature, address[] memory deniers) external; - - function setFeatureFlagAllowAll(bytes32 feature, bool allowAll) external; - - function setFeatureFlagDenyAll(bytes32 feature, bool denyAll) external; - - error FeatureUnavailable(bytes32 which); - error InvalidAccountId(uint128 accountId); - error InvalidPermission(bytes32 permission); - error OnlyAccountTokenProxy(address origin); - error PermissionDenied( - uint128 accountId, bytes32 permission, address target - ); - error PermissionNotGranted( - uint128 accountId, bytes32 permission, address user - ); - error PositionOutOfBounds(); - - event AccountCreated(uint128 indexed accountId, address indexed owner); - event PermissionGranted( - uint128 indexed accountId, - bytes32 indexed permission, - address indexed user, - address sender - ); - event PermissionRevoked( - uint128 indexed accountId, - bytes32 indexed permission, - address indexed user, - address sender - ); - - function createAccount() external returns (uint128 accountId); - - function createAccount(uint128 requestedAccountId) external; - - function getAccountLastInteraction(uint128 accountId) - external - view - returns (uint256); - - function getAccountOwner(uint128 accountId) - external - view - returns (address); - - function getAccountPermissions(uint128 accountId) - external - view - returns (IAccountModule.AccountPermissions[] memory accountPerms); - - function getAccountTokenAddress() external view returns (address); - - function grantPermission( - uint128 accountId, - bytes32 permission, - address user - ) - external; - - function hasPermission( - uint128 accountId, - bytes32 permission, - address user - ) - external - view - returns (bool); - - function isAuthorized( - uint128 accountId, - bytes32 permission, - address user - ) - external - view - returns (bool); - - function notifyAccountTransfer(address to, uint128 accountId) external; - - function renouncePermission( - uint128 accountId, - bytes32 permission - ) - external; - - function revokePermission( - uint128 accountId, - bytes32 permission, - address user - ) - external; - - error AccountNotFound(uint128 accountId); - error EmptyDistribution(); - error InsufficientCollateralRatio( - uint256 collateralValue, uint256 debt, uint256 ratio, uint256 minRatio - ); - error MarketNotFound(uint128 marketId); - error NotFundedByPool(uint256 marketId, uint256 poolId); - error OverflowInt256ToInt128(); - error OverflowInt256ToUint256(); - error OverflowUint128ToInt128(); - error OverflowUint256ToInt256(); - error OverflowUint256ToUint128(); - - event DebtAssociated( - uint128 indexed marketId, - uint128 indexed poolId, - address indexed collateralType, - uint128 accountId, - uint256 amount, - int256 updatedDebt - ); - - function associateDebt( - uint128 marketId, - uint128 poolId, - address collateralType, - uint128 accountId, - uint256 amount - ) - external - returns (int256); - - error MismatchAssociatedSystemKind(bytes32 expected, bytes32 actual); - error MissingAssociatedSystem(bytes32 id); - - event AssociatedSystemSet( - bytes32 indexed kind, bytes32 indexed id, address proxy, address impl - ); - - function getAssociatedSystem(bytes32 id) - external - view - returns (address addr, bytes32 kind); - - function initOrUpgradeNft( - bytes32 id, - string memory name, - string memory symbol, - string memory uri, - address impl - ) - external; - - function initOrUpgradeToken( - bytes32 id, - string memory name, - string memory symbol, - uint8 decimals, - address impl - ) - external; - - function registerUnmanagedSystem(bytes32 id, address endpoint) external; - - error AccountActivityTimeoutPending( - uint128 accountId, uint256 currentTime, uint256 requiredTime - ); - error CollateralDepositDisabled(address collateralType); - error CollateralNotFound(); - error FailedTransfer(address from, address to, uint256 value); - error InsufficientAccountCollateral(uint256 amount); - error InsufficientAllowance(uint256 required, uint256 existing); - error InvalidParameter(string parameter, string reason); - error OverflowUint256ToUint64(); - error PrecisionLost(uint256 tokenAmount, uint8 decimals); - - event CollateralLockCreated( - uint128 indexed accountId, - address indexed collateralType, - uint256 tokenAmount, - uint64 expireTimestamp - ); - event CollateralLockExpired( - uint128 indexed accountId, - address indexed collateralType, - uint256 tokenAmount, - uint64 expireTimestamp - ); - event Deposited( - uint128 indexed accountId, - address indexed collateralType, - uint256 tokenAmount, - address indexed sender - ); - event Withdrawn( - uint128 indexed accountId, - address indexed collateralType, - uint256 tokenAmount, - address indexed sender - ); - - function cleanExpiredLocks( - uint128 accountId, - address collateralType, - uint256 offset, - uint256 count - ) - external - returns (uint256 cleared); - - function createLock( - uint128 accountId, - address collateralType, - uint256 amount, - uint64 expireTimestamp - ) - external; - - function deposit( - uint128 accountId, - address collateralType, - uint256 tokenAmount - ) - external; - - function getAccountAvailableCollateral( - uint128 accountId, - address collateralType - ) - external - view - returns (uint256); - - function getAccountCollateral( - uint128 accountId, - address collateralType - ) - external - view - returns ( - uint256 totalDeposited, - uint256 totalAssigned, - uint256 totalLocked - ); - - function getLocks( - uint128 accountId, - address collateralType, - uint256 offset, - uint256 count - ) - external - view - returns (CollateralLock.Data[] memory locks); - - function withdraw( - uint128 accountId, - address collateralType, - uint256 tokenAmount - ) - external; - - event CollateralConfigured( - address indexed collateralType, CollateralConfiguration.Data config - ); - - function configureCollateral(CollateralConfiguration.Data memory config) - external; - - function getCollateralConfiguration(address collateralType) - external - view - returns (CollateralConfiguration.Data memory); - - function getCollateralConfigurations(bool hideDisabled) - external - view - returns (CollateralConfiguration.Data[] memory); - - function getCollateralPrice(address collateralType) - external - view - returns (uint256); - - error InsufficientDebt(int256 currentDebt); - error PoolNotFound(uint128 poolId); - - event IssuanceFeePaid( - uint128 indexed accountId, - uint128 indexed poolId, - address collateralType, - uint256 feeAmount - ); - event UsdBurned( - uint128 indexed accountId, - uint128 indexed poolId, - address collateralType, - uint256 amount, - address indexed sender - ); - event UsdMinted( - uint128 indexed accountId, - uint128 indexed poolId, - address collateralType, - uint256 amount, - address indexed sender - ); - - function burnUsd( - uint128 accountId, - uint128 poolId, - address collateralType, - uint256 amount - ) - external; - - function mintUsd( - uint128 accountId, - uint128 poolId, - address collateralType, - uint256 amount - ) - external; - - error CannotScaleEmptyMapping(); - error IneligibleForLiquidation( - uint256 collateralValue, - int256 debt, - uint256 currentCRatio, - uint256 cratio - ); - error InsufficientMappedAmount(); - error MustBeVaultLiquidated(); - error OverflowInt128ToUint128(); - - event Liquidation( - uint128 indexed accountId, - uint128 indexed poolId, - address indexed collateralType, - ILiquidationModule.LiquidationData liquidationData, - uint128 liquidateAsAccountId, - address sender - ); - event VaultLiquidation( - uint128 indexed poolId, - address indexed collateralType, - ILiquidationModule.LiquidationData liquidationData, - uint128 liquidateAsAccountId, - address sender - ); - - function isPositionLiquidatable( - uint128 accountId, - uint128 poolId, - address collateralType - ) - external - returns (bool); - - function isVaultLiquidatable( - uint128 poolId, - address collateralType - ) - external - returns (bool); - - function liquidate( - uint128 accountId, - uint128 poolId, - address collateralType, - uint128 liquidateAsAccountId - ) - external - returns (ILiquidationModule.LiquidationData memory liquidationData); - - function liquidateVault( - uint128 poolId, - address collateralType, - uint128 liquidateAsAccountId, - uint256 maxUsd - ) - external - returns (ILiquidationModule.LiquidationData memory liquidationData); - - error InsufficientMarketCollateralDepositable( - uint128 marketId, address collateralType, uint256 tokenAmountToDeposit - ); - error InsufficientMarketCollateralWithdrawable( - uint128 marketId, address collateralType, uint256 tokenAmountToWithdraw - ); - - event MarketCollateralDeposited( - uint128 indexed marketId, - address indexed collateralType, - uint256 tokenAmount, - address indexed sender - ); - event MarketCollateralWithdrawn( - uint128 indexed marketId, - address indexed collateralType, - uint256 tokenAmount, - address indexed sender - ); - event MaximumMarketCollateralConfigured( - uint128 indexed marketId, - address indexed collateralType, - uint256 systemAmount, - address indexed owner - ); - - function configureMaximumMarketCollateral( - uint128 marketId, - address collateralType, - uint256 amount - ) - external; - - function depositMarketCollateral( - uint128 marketId, - address collateralType, - uint256 tokenAmount - ) - external; - - function getMarketCollateralAmount( - uint128 marketId, - address collateralType - ) - external - view - returns (uint256 collateralAmountD18); - - function getMarketCollateralValue(uint128 marketId) - external - view - returns (uint256); - - function getMaximumMarketCollateral( - uint128 marketId, - address collateralType - ) - external - view - returns (uint256); - - function withdrawMarketCollateral( - uint128 marketId, - address collateralType, - uint256 tokenAmount - ) - external; - - error IncorrectMarketInterface(address market); - error NotEnoughLiquidity(uint128 marketId, uint256 amount); - - event MarketRegistered( - address indexed market, uint128 indexed marketId, address indexed sender - ); - event MarketSystemFeePaid(uint128 indexed marketId, uint256 feeAmount); - event MarketUsdDeposited( - uint128 indexed marketId, - address indexed target, - uint256 amount, - address indexed market - ); - event MarketUsdWithdrawn( - uint128 indexed marketId, - address indexed target, - uint256 amount, - address indexed market - ); - event SetMarketMinLiquidityRatio( - uint128 indexed marketId, uint256 minLiquidityRatio - ); - event SetMinDelegateTime(uint128 indexed marketId, uint32 minDelegateTime); - - function depositMarketUsd( - uint128 marketId, - address target, - uint256 amount - ) - external - returns (uint256 feeAmount); - - function distributeDebtToPools( - uint128 marketId, - uint256 maxIter - ) - external - returns (bool); - - function getMarketCollateral(uint128 marketId) - external - view - returns (uint256); - - function getMarketDebtPerShare(uint128 marketId) - external - returns (int256); - - function getMarketFees( - uint128, - uint256 amount - ) - external - view - returns (uint256 depositFeeAmount, uint256 withdrawFeeAmount); - - function getMarketMinDelegateTime(uint128 marketId) - external - view - returns (uint32); - - function getMarketNetIssuance(uint128 marketId) - external - view - returns (int128); - - function getMarketReportedDebt(uint128 marketId) - external - view - returns (uint256); - - function getMarketTotalDebt(uint128 marketId) - external - view - returns (int256); - - function getMinLiquidityRatio(uint128 marketId) - external - view - returns (uint256); - - function getOracleManager() external view returns (address); - - function getUsdToken() external view returns (address); - - function getWithdrawableMarketUsd(uint128 marketId) - external - view - returns (uint256); - - function isMarketCapacityLocked(uint128 marketId) - external - view - returns (bool); - - function registerMarket(address market) - external - returns (uint128 marketId); - - function setMarketMinDelegateTime( - uint128 marketId, - uint32 minDelegateTime - ) - external; - - function setMinLiquidityRatio( - uint128 marketId, - uint256 minLiquidityRatio - ) - external; - - function withdrawMarketUsd( - uint128 marketId, - address target, - uint256 amount - ) - external - returns (uint256 feeAmount); - - function multicall(bytes[] memory data) - external - payable - returns (bytes[] memory results); - - event PoolApprovedAdded(uint256 poolId); - event PoolApprovedRemoved(uint256 poolId); - event PreferredPoolSet(uint256 poolId); - - function addApprovedPool(uint128 poolId) external; - - function getApprovedPools() external view returns (uint256[] memory); - - function getPreferredPool() external view returns (uint128); - - function removeApprovedPool(uint128 poolId) external; - - function setPreferredPool(uint128 poolId) external; - - error CapacityLocked(uint256 marketId); - error MinDelegationTimeoutPending(uint128 poolId, uint32 timeRemaining); - error PoolAlreadyExists(uint128 poolId); - - event PoolConfigurationSet( - uint128 indexed poolId, - MarketConfiguration.Data[] markets, - address indexed sender - ); - event PoolCreated( - uint128 indexed poolId, address indexed owner, address indexed sender - ); - event PoolNameUpdated( - uint128 indexed poolId, string name, address indexed sender - ); - event PoolNominationRenounced( - uint128 indexed poolId, address indexed owner - ); - event PoolNominationRevoked(uint128 indexed poolId, address indexed owner); - event PoolOwnerNominated( - uint128 indexed poolId, - address indexed nominatedOwner, - address indexed owner - ); - event PoolOwnershipAccepted(uint128 indexed poolId, address indexed owner); - event SetMinLiquidityRatio(uint256 minLiquidityRatio); - - function acceptPoolOwnership(uint128 poolId) external; - - function createPool(uint128 requestedPoolId, address owner) external; - - function getMinLiquidityRatio() external view returns (uint256); - - function getNominatedPoolOwner(uint128 poolId) - external - view - returns (address); - - function getPoolConfiguration(uint128 poolId) - external - view - returns (MarketConfiguration.Data[] memory); - - function getPoolName(uint128 poolId) - external - view - returns (string memory poolName); - - function getPoolOwner(uint128 poolId) external view returns (address); - - function nominatePoolOwner( - address nominatedOwner, - uint128 poolId - ) - external; - - function renouncePoolNomination(uint128 poolId) external; - - function revokePoolNomination(uint128 poolId) external; - - function setMinLiquidityRatio(uint256 minLiquidityRatio) external; - - function setPoolConfiguration( - uint128 poolId, - MarketConfiguration.Data[] memory newMarketConfigurations - ) - external; - - function setPoolName(uint128 poolId, string memory name) external; - - error OverflowUint256ToUint32(); - error OverflowUint32ToInt32(); - error OverflowUint64ToInt64(); - error RewardDistributorNotFound(); - error RewardUnavailable(address distributor); - - event RewardsClaimed( - uint128 indexed accountId, - uint128 indexed poolId, - address indexed collateralType, - address distributor, - uint256 amount - ); - event RewardsDistributed( - uint128 indexed poolId, - address indexed collateralType, - address distributor, - uint256 amount, - uint256 start, - uint256 duration - ); - event RewardsDistributorRegistered( - uint128 indexed poolId, - address indexed collateralType, - address indexed distributor - ); - event RewardsDistributorRemoved( - uint128 indexed poolId, - address indexed collateralType, - address indexed distributor - ); - - function claimRewards( - uint128 accountId, - uint128 poolId, - address collateralType, - address distributor - ) - external - returns (uint256); - - function distributeRewards( - uint128 poolId, - address collateralType, - uint256 amount, - uint64 start, - uint32 duration - ) - external; - - function getRewardRate( - uint128 poolId, - address collateralType, - address distributor - ) - external - view - returns (uint256); - - function registerRewardsDistributor( - uint128 poolId, - address collateralType, - address distributor - ) - external; - - function removeRewardsDistributor( - uint128 poolId, - address collateralType, - address distributor - ) - external; - - function updateRewards( - uint128 poolId, - address collateralType, - uint128 accountId - ) - external - returns (uint256[] memory, address[] memory); - - function configureOracleManager(address oracleManagerAddress) external; - - function getConfig(bytes32 k) external view returns (bytes32 v); - - function registerCcip( - address ccipSend, - address ccipReceive, - address ccipTokenPool - ) - external; - - function setConfig(bytes32 k, bytes32 v) external; - - error InsufficientDelegation(uint256 minDelegation); - error InvalidCollateralAmount(); - error InvalidLeverage(uint256 leverage); - - event DelegationUpdated( - uint128 indexed accountId, - uint128 indexed poolId, - address collateralType, - uint256 amount, - uint256 leverage, - address indexed sender - ); - - function delegateCollateral( - uint128 accountId, - uint128 poolId, - address collateralType, - uint256 newCollateralAmountD18, - uint256 leverage - ) - external; - - function getPosition( - uint128 accountId, - uint128 poolId, - address collateralType - ) - external - returns ( - uint256 collateralAmount, - uint256 collateralValue, - int256 debt, - uint256 collateralizationRatio - ); - - function getPositionCollateral( - uint128 accountId, - uint128 poolId, - address collateralType - ) - external - view - returns (uint256 amount, uint256 value); - - function getPositionCollateralRatio( - uint128 accountId, - uint128 poolId, - address collateralType - ) - external - returns (uint256); - - function getPositionDebt( - uint128 accountId, - uint128 poolId, - address collateralType - ) - external - returns (int256); - - function getVaultCollateral( - uint128 poolId, - address collateralType - ) - external - view - returns (uint256 amount, uint256 value); - - function getVaultCollateralRatio( - uint128 poolId, - address collateralType - ) - external - returns (uint256); - - function getVaultDebt( - uint128 poolId, - address collateralType - ) - external - returns (int256); - -} - -interface IAccountModule { - - struct AccountPermissions { - address user; - bytes32[] permissions; - } - -} - -interface CollateralLock { - - struct Data { - uint128 amountD18; - uint64 lockExpirationTime; - } - -} - -interface CollateralConfiguration { - - struct Data { - bool depositingEnabled; - uint256 issuanceRatioD18; - uint256 liquidationRatioD18; - uint256 liquidationRewardD18; - bytes32 oracleNodeId; - address tokenAddress; - uint256 minDelegationD18; - } - -} - -interface ILiquidationModule { - - struct LiquidationData { - uint256 debtLiquidated; - uint256 collateralLiquidated; - uint256 amountRewarded; - } - -} - -interface MarketConfiguration { - - struct Data { - uint128 marketId; - uint128 weightD18; - int128 maxDebtShareValueD18; - } - } From 9797ef2563ee61cf1650368804917c2dd5604fe9 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:39:29 -0400 Subject: [PATCH 081/129] =?UTF-8?q?=E2=9C=85=20add=20test=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/interfaces/ISynthetix.sol | 103 +++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 test/interfaces/ISynthetix.sol diff --git a/test/interfaces/ISynthetix.sol b/test/interfaces/ISynthetix.sol new file mode 100644 index 0000000..87aaf3c --- /dev/null +++ b/test/interfaces/ISynthetix.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +interface ISpotMarket { + + struct Data { + uint256 fixedFees; + uint256 utilizationFees; + int256 skewFees; + int256 wrapperFees; + } + + function getSynth(uint128 marketId) + external + view + returns (address synthAddress); + + function wrap( + uint128 marketId, + uint256 wrapAmount, + uint256 minAmountReceived + ) + external + returns (uint256 amountToMint, Data memory fees); + + function unwrap( + uint128 marketId, + uint256 unwrapAmount, + uint256 minAmountReceived + ) + external + returns (uint256 returnCollateralAmount, Data memory fees); + + function buy( + uint128 marketId, + uint256 usdAmount, + uint256 minAmountReceived, + address referrer + ) + external + returns (uint256 synthAmount, Data memory fees); + + function sell( + uint128 marketId, + uint256 synthAmount, + uint256 minUsdAmount, + address referrer + ) + external + returns (uint256 usdAmountReceived, Data memory fees); + +} + +interface IPerpsMarket { + + function modifyCollateral( + uint128 accountId, + uint128 synthMarketId, + int256 amountDelta + ) + external; + + function renouncePermission( + uint128 accountId, + bytes32 permission + ) + external; + + function isAuthorized( + uint128 accountId, + bytes32 permission, + address target + ) + external + view + returns (bool isAuthorized); + + function payDebt(uint128 accountId, uint256 amount) external; + + function debt(uint128 accountId) + external + view + returns (uint256 accountDebt); + + function hasPermission( + uint128 accountId, + bytes32 permission, + address user + ) + external + view + returns (bool); + + function createAccount() external returns (uint128 accountId); + + function grantPermission( + uint128 accountId, + bytes32 permission, + address user + ) + external; + +} From d3ef40a1caf85127a35007d8d0680d6eefc925d4 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:39:59 -0400 Subject: [PATCH 082/129] =?UTF-8?q?=F0=9F=93=9A=20improve=20natspec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/utils/errors/SynthetixV3Errors.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utils/errors/SynthetixV3Errors.sol b/test/utils/errors/SynthetixV3Errors.sol index 1660ba6..416317a 100644 --- a/test/utils/errors/SynthetixV3Errors.sol +++ b/test/utils/errors/SynthetixV3Errors.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -/// @title Cosolidated Errors from Synthetix v3 contracts -/// @author JaredBorders (jaredborders@pm.me) +/// @title cosolidated custom errors from synthetix v3 contracts +/// @dev helps with debugging errors thrown by synthetix v3 contract SynthetixV3Errors { enum SettlementStrategyType { From 3ea57f96b0d7e5196a16d28e5ca8c73c44d0a6b1 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:40:12 -0400 Subject: [PATCH 083/129] =?UTF-8?q?=E2=9C=85=20add=20constants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/utils/Constants.sol | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index 8e7184c..b443359 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -3,20 +3,21 @@ pragma solidity 0.8.27; contract Constants { - /// @custom:zap + /// @custom:forks string constant BASE_RPC_REF = "BASE_RPC"; string constant ARBITRUM_RPC_REF = "ARBITRUM_RPC"; + string constant ARBITRUM_SEPOLIA_RPC_REF = "ARBITRUM_SEPOLIA_RPC"; uint256 constant BASE_FORK_BLOCK = 20_165_000; - uint256 constant ARBITRUM_FORK_BLOCK_DEBT = 256_215_787; uint256 constant ARBITRUM_FORK_BLOCK = 256_615_000; + uint256 constant ARBITRUM_SEPOLIA_FORK_BLOCK = 85_443_000; + + /// @custom:values address constant ACTOR = 0x7777777777777777777777777777777777777777; - address constant ARBITRUM_BOB = 0x3bd5e344Ac629C9f232F921bAfDeEEc312deAC9b; - uint128 constant ARBITRUM_BOB_ID = - 170_141_183_460_469_231_731_687_303_715_884_105_912; uint256 constant DEFAULT_TOLERANCE = 0; /// @custom:tokens address constant ARBITRUM_WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + address constant ARBITRUM_SEPOLIA_WETH = address(0); address constant BASE_WETH = 0x4200000000000000000000000000000000000006; /// @custom:synthetix From 8db4be522751ee6cac99d586b4113e35953e02b2 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:40:25 -0400 Subject: [PATCH 084/129] =?UTF-8?q?=E2=9C=85=20refactor=20bootstrap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/utils/Bootstrap.sol | 143 +++++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol index d1f5c11..d2d27a2 100644 --- a/test/utils/Bootstrap.sol +++ b/test/utils/Bootstrap.sol @@ -1,32 +1,35 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {Arbitrum, Base} from "../../script/utils/Parameters.sol"; +import {Deploy} from "../../script/Deploy.s.sol"; import { - Errors, - ICore, - IERC20, - IPerpsMarket, - IPool, - ISpotMarket, - Reentrancy, - Zap -} from "../../src/Zap.sol"; + Arbitrum, ArbitrumSepolia, Base +} from "../../script/utils/Parameters.sol"; +import {Errors, IERC20, IPool, Reentrancy, Zap} from "../../src/Zap.sol"; +import {IPerpsMarket, ISpotMarket} from "../interfaces/ISynthetix.sol"; import {Constants} from "../utils/Constants.sol"; import {Test} from "forge-std/Test.sol"; -contract Bootstrap is Test, Base, Arbitrum, Constants { +contract Bootstrap is + Test, + Deploy, + Base, + Arbitrum, + ArbitrumSepolia, + Constants +{ + /// @custom:forks uint256 BASE; - uint256 ARBITRUM_A; - uint256 ARBITRUM_B; + uint256 ARBITRUM; + uint256 ARBITRUM_SEPOLIA; + /// @custom:target Zap zap; - ICore core; + /// @custom:auxiliary ISpotMarket spotMarket; IPerpsMarket perpsMarket; - IERC20 usdc; IERC20 susdc; IERC20 usdx; @@ -35,81 +38,105 @@ contract Bootstrap is Test, Base, Arbitrum, Constants { function setUp() public { string memory BASE_RPC = vm.envString(BASE_RPC_REF); string memory ARBITRUM_RPC = vm.envString(ARBITRUM_RPC_REF); + string memory ARBITRUM_SEPOLIA_RPC = + vm.envString(ARBITRUM_SEPOLIA_RPC_REF); BASE = vm.createFork(BASE_RPC, BASE_FORK_BLOCK); - ARBITRUM_A = vm.createFork(ARBITRUM_RPC, ARBITRUM_FORK_BLOCK); - ARBITRUM_B = vm.createFork(ARBITRUM_RPC, ARBITRUM_FORK_BLOCK_DEBT); + ARBITRUM = vm.createFork(ARBITRUM_RPC, ARBITRUM_FORK_BLOCK); + ARBITRUM_SEPOLIA = + vm.createFork(ARBITRUM_SEPOLIA_RPC, ARBITRUM_SEPOLIA_FORK_BLOCK); } modifier base() { + /// @custom:fork vm.selectFork(BASE); - zap = new Zap({ - _usdc: BASE_USDC, - _usdx: BASE_USDX, - _spotMarket: BASE_SPOT_MARKET, - _perpsMarket: BASE_PERPS_MARKET, - _core: BASE_CORE, - _referrer: BASE_REFERRER, - _susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, - _aave: BASE_AAVE_POOL, - _uniswap: BASE_UNISWAP + + /// @custom:target + zap = deploySystem({ + usdc: BASE_USDC, + usdx: BASE_USDX, + spotMarket: BASE_SPOT_MARKET, + perpsMarket: BASE_PERPS_MARKET, + referrer: BASE_REFERRER, + susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, + aave: BASE_AAVE_POOL, + router: BASE_ROUTER, + quoter: BASE_QUOTER }); - core = ICore(BASE_CORE); + + /// @custom:auxiliary spotMarket = ISpotMarket(BASE_SPOT_MARKET); perpsMarket = IPerpsMarket(BASE_PERPS_MARKET); usdc = IERC20(BASE_USDC); susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); usdx = IERC20(BASE_USDX); weth = IERC20(BASE_WETH); + _; } modifier arbitrum() { - vm.selectFork(ARBITRUM_A); - zap = new Zap({ - _usdc: ARBITRUM_USDC, - _usdx: ARBITRUM_USDX, - _spotMarket: ARBITRUM_SPOT_MARKET, - _perpsMarket: ARBITRUM_PERPS_MARKET, - _core: ARBITRUM_CORE, - _referrer: ARBITRUM_REFERRER, - _susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, - _aave: ARBITRUM_AAVE_POOL, - _uniswap: ARBITRUM_UNISWAP + /// @custom:fork + vm.selectFork(ARBITRUM); + + /// @custom:target + zap = deploySystem({ + usdc: ARBITRUM_USDC, + usdx: ARBITRUM_USDX, + spotMarket: ARBITRUM_SPOT_MARKET, + perpsMarket: ARBITRUM_PERPS_MARKET, + referrer: ARBITRUM_REFERRER, + susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, + aave: ARBITRUM_AAVE_POOL, + router: ARBITRUM_ROUTER, + quoter: ARBITRUM_QUOTER }); - core = ICore(ARBITRUM_CORE); + + /// @custom:auxiliary spotMarket = ISpotMarket(ARBITRUM_SPOT_MARKET); perpsMarket = IPerpsMarket(ARBITRUM_PERPS_MARKET); usdc = IERC20(ARBITRUM_USDC); susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); usdx = IERC20(ARBITRUM_USDX); weth = IERC20(ARBITRUM_WETH); + _; } - modifier arbitrum_b() { - vm.selectFork(ARBITRUM_B); - zap = new Zap({ - _usdc: ARBITRUM_USDC, - _usdx: ARBITRUM_USDX, - _spotMarket: ARBITRUM_SPOT_MARKET, - _perpsMarket: ARBITRUM_PERPS_MARKET, - _core: ARBITRUM_CORE, - _referrer: ARBITRUM_REFERRER, - _susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, - _aave: ARBITRUM_AAVE_POOL, - _uniswap: ARBITRUM_UNISWAP + modifier arbitrum_sepolia() { + /// @custom:fork + vm.selectFork(ARBITRUM_SEPOLIA); + + /// @custom:target + zap = deploySystem({ + usdc: ARBITRUM_SEPOLIA_USDC, + usdx: ARBITRUM_SEPOLIA_USDX, + spotMarket: ARBITRUM_SEPOLIA_SPOT_MARKET, + perpsMarket: ARBITRUM_SEPOLIA_PERPS_MARKET, + referrer: ARBITRUM_SEPOLIA_REFERRER, + susdcSpotId: ARBITRUM_SEPOLIA_SUSDC_SPOT_MARKET_ID, + aave: ARBITRUM_SEPOLIA_AAVE_POOL, + router: ARBITRUM_SEPOLIA_ROUTER, + quoter: ARBITRUM_SEPOLIA_QUOTER }); - core = ICore(ARBITRUM_CORE); - spotMarket = ISpotMarket(ARBITRUM_SPOT_MARKET); - perpsMarket = IPerpsMarket(ARBITRUM_PERPS_MARKET); - usdc = IERC20(ARBITRUM_USDC); + + /// @custom:auxiliary + spotMarket = ISpotMarket(ARBITRUM_SEPOLIA_SPOT_MARKET); + perpsMarket = IPerpsMarket(ARBITRUM_SEPOLIA_PERPS_MARKET); + usdc = IERC20(ARBITRUM_SEPOLIA_USDC); susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); - usdx = IERC20(ARBITRUM_USDX); - weth = IERC20(ARBITRUM_WETH); + usdx = IERC20(ARBITRUM_SEPOLIA_USDX); + weth = IERC20(ARBITRUM_SEPOLIA_WETH); + _; } + /// @notice "spin" up an EOA with some tokens and approvals + /// @dev "assumes" a minimum amount thereby muffling fuzzing noise + /// @param eoa to spin up + /// @param token that will be sent (i.e., dealt) to the EOA + /// @param amount of tken to send to the EOA + /// @param approved address granted max allowance by the EOA function _spin( address eoa, IERC20 token, From 66df3a52393593567456978cc1df6aaab3bb2417 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:40:37 -0400 Subject: [PATCH 085/129] =?UTF-8?q?=E2=9C=85=20update=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Aave.t.sol | 20 +------------------- test/Burn.t.sol | 6 ++++-- test/Buy.t.sol | 1 - test/Reentrancy.t.sol | 1 - test/Sell.t.sol | 1 - test/Swap.for.t.sol | 4 +++- test/Swap.with.t.sol | 4 +++- test/Unwind.t.sol | 14 ++------------ test/Unwrap.t.sol | 1 - test/Withdraw.t.sol | 1 - test/Wrap.t.sol | 1 - test/Zap.in.t.sol | 1 - test/Zap.out.t.sol | 1 - 13 files changed, 13 insertions(+), 43 deletions(-) diff --git a/test/Aave.t.sol b/test/Aave.t.sol index c133a7d..164d388 100644 --- a/test/Aave.t.sol +++ b/test/Aave.t.sol @@ -5,7 +5,6 @@ import { Bootstrap, Constants, Errors, - ICore, IERC20, IPerpsMarket, IPool, @@ -16,7 +15,7 @@ import { contract AaveTest is Bootstrap, Errors { - function test_only_aave_arbitrum( + function test_executeOperation_only_aave( address caller, address a, uint256 b, @@ -33,21 +32,4 @@ contract AaveTest is Bootstrap, Errors { } } - function test_only_aave_base( - address caller, - address a, - uint256 b, - uint256 c, - bytes calldata d - ) - public - base - { - if (caller != zap.AAVE()) { - vm.prank(caller); - vm.expectRevert(abi.encodeWithSelector(OnlyAave.selector, caller)); - zap.executeOperation(a, b, c, a, d); - } - } - } diff --git a/test/Burn.t.sol b/test/Burn.t.sol index 6b745c4..d11021e 100644 --- a/test/Burn.t.sol +++ b/test/Burn.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, @@ -16,6 +15,9 @@ import { contract BurnTest is Bootstrap { /// @custom:todo - function test_burn_arbitrum_sepolia(uint32 amount) public {} + function test_burn_arbitrum_sepolia(uint32 amount) + public + arbitrum_sepolia + {} } diff --git a/test/Buy.t.sol b/test/Buy.t.sol index bc05364..190d4e7 100644 --- a/test/Buy.t.sol +++ b/test/Buy.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, diff --git a/test/Reentrancy.t.sol b/test/Reentrancy.t.sol index 62915cd..f0b626e 100644 --- a/test/Reentrancy.t.sol +++ b/test/Reentrancy.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, diff --git a/test/Sell.t.sol b/test/Sell.t.sol index cf83475..b4476dd 100644 --- a/test/Sell.t.sol +++ b/test/Sell.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, diff --git a/test/Swap.for.t.sol b/test/Swap.for.t.sol index 85aa475..10e7d57 100644 --- a/test/Swap.for.t.sol +++ b/test/Swap.for.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, @@ -21,4 +20,7 @@ contract SwapForTest is Bootstrap { /// @custom:todo function test_swap_for_arbitrum() public arbitrum {} + /// @custom:todo + function test_swap_for_arbitrum_sepolia() public arbitrum_sepolia {} + } diff --git a/test/Swap.with.t.sol b/test/Swap.with.t.sol index 363c6d6..ddd5d35 100644 --- a/test/Swap.with.t.sol +++ b/test/Swap.with.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, @@ -21,4 +20,7 @@ contract SwapWithTest is Bootstrap { /// @custom:todo function test_swap_with_arbitrum() public arbitrum {} + /// @custom:todo + function test_swap_with_arbitrum_sepolia() public arbitrum_sepolia {} + } diff --git a/test/Unwind.t.sol b/test/Unwind.t.sol index 3015b1b..e1ac19c 100644 --- a/test/Unwind.t.sol +++ b/test/Unwind.t.sol @@ -5,7 +5,6 @@ import { Bootstrap, Constants, Errors, - ICore, IERC20, IPerpsMarket, IPool, @@ -16,22 +15,13 @@ import { contract UnwindTest is Bootstrap, Errors { - function test_unwind_is_authorized_arbitrum() public arbitrum { + function test_unwind_is_authorized() public arbitrum { vm.prank(ACTOR); vm.expectRevert(NotPermitted.selector); zap.unwind(0, 0, 0, address(0), 0, 0, 0, address(0)); } - function test_unwind_is_authorized_base() public base { - vm.prank(ACTOR); - vm.expectRevert(NotPermitted.selector); - zap.unwind(0, 0, 0, address(0), 0, 0, 0, address(0)); - } - - /// @custom:todo - function test_unwind_arbitrum_sepolia() public {} - /// @custom:todo - function test_unwind_arbitrum() public {} + function test_unwind_arbitrum_sepolia() public arbitrum_sepolia {} } diff --git a/test/Unwrap.t.sol b/test/Unwrap.t.sol index 9c13099..b900b7c 100644 --- a/test/Unwrap.t.sol +++ b/test/Unwrap.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, diff --git a/test/Withdraw.t.sol b/test/Withdraw.t.sol index 1954285..03e11e5 100644 --- a/test/Withdraw.t.sol +++ b/test/Withdraw.t.sol @@ -5,7 +5,6 @@ import { Bootstrap, Constants, Errors, - ICore, IERC20, IPerpsMarket, IPool, diff --git a/test/Wrap.t.sol b/test/Wrap.t.sol index dd829b9..ba7d088 100644 --- a/test/Wrap.t.sol +++ b/test/Wrap.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, diff --git a/test/Zap.in.t.sol b/test/Zap.in.t.sol index 9d18652..4596acb 100644 --- a/test/Zap.in.t.sol +++ b/test/Zap.in.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, diff --git a/test/Zap.out.t.sol b/test/Zap.out.t.sol index cc907c8..6f3c92a 100644 --- a/test/Zap.out.t.sol +++ b/test/Zap.out.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import { Bootstrap, Constants, - ICore, IERC20, IPerpsMarket, IPool, From 5bdfa7f98a8ff46ef8e7c38f7d56a4ed0bc9c968 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Tue, 1 Oct 2024 16:44:40 -0400 Subject: [PATCH 086/129] =?UTF-8?q?=F0=9F=93=B8=20update=20lcov/gas-snapsh?= =?UTF-8?q?ot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 65 ++++---- lcov.info | 451 +++++++++++++++++++++++++------------------------- 2 files changed, 257 insertions(+), 259 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 94b5c4c..a8905ed 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,33 +1,32 @@ -AaveTest:test_only_aave_arbitrum(address,address,uint256,uint256,bytes) (runs: 264, μ: 3569236, ~: 3569235) -AaveTest:test_only_aave_base(address,address,uint256,uint256,bytes) (runs: 264, μ: 3569279, ~: 3569278) -BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 264, μ: 434, ~: 434) -BuyTest:test_buy_arbitrum(uint32) (runs: 258, μ: 4124020, ~: 4124020) -BuyTest:test_buy_base(uint32) (runs: 258, μ: 4043670, ~: 4043670) -ReentrancyTest:test_requireStage(uint8) (runs: 259, μ: 15965, ~: 6343) -ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 16405, ~: 8875) -ReentrancyTest:test_stage_default() (gas: 2435) -ReentrancyTest:test_stage_enum_level1() (gas: 250) -ReentrancyTest:test_stage_enum_level2() (gas: 294) -ReentrancyTest:test_stage_enum_unset() (gas: 293) -SellTest:test_sell_arbitrum(uint32) (runs: 258, μ: 4293543, ~: 4293543) -SellTest:test_sell_base(uint32) (runs: 258, μ: 4195005, ~: 4195005) -SwapForTest:test_swap_for_arbitrum() (gas: 3562157) -SwapForTest:test_swap_for_base() (gas: 3562178) -SwapWithTest:test_swap_with_arbitrum() (gas: 3562178) -SwapWithTest:test_swap_with_base() (gas: 3562201) -UnwindTest:test_unwind_arbitrum() (gas: 167) -UnwindTest:test_unwind_arbitrum_sepolia() (gas: 211) -UnwindTest:test_unwind_is_authorized_arbitrum() (gas: 3586536) -UnwindTest:test_unwind_is_authorized_base() (gas: 3586580) -UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 258, μ: 4337781, ~: 4337781) -UnwrapTest:test_unwrap_base(uint32) (runs: 258, μ: 4229739, ~: 4229739) -WithdrawTest:test_withdraw_arbitrum() (gas: 4406465) -WithdrawTest:test_withdraw_base() (gas: 4229858) -WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 3585402) -WithdrawTest:test_withdraw_is_authorized_base() (gas: 3585423) -WrapTest:test_wrap_arbitrum(uint32) (runs: 258, μ: 4159384, ~: 4159384) -WrapTest:test_wrap_base(uint32) (runs: 258, μ: 4069009, ~: 4069009) -ZapInTest:test_zap_in_arbitrum(uint32) (runs: 259, μ: 4292469, ~: 4292469) -ZapInTest:test_zap_in_base(uint32) (runs: 259, μ: 4189486, ~: 4189486) -ZapOutTest:test_zap_out_arbitum(uint32) (runs: 258, μ: 4242613, ~: 4242613) -ZapOutTest:test_zap_out_base(uint32) (runs: 258, μ: 4144528, ~: 4144528) \ No newline at end of file +AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 264, μ: 3225005, ~: 3225004) +BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 264, μ: 3201214, ~: 3201214) +BuyTest:test_buy_arbitrum(uint32) (runs: 258, μ: 3737678, ~: 3737678) +BuyTest:test_buy_base(uint32) (runs: 258, μ: 3657317, ~: 3657317) +ReentrancyTest:test_requireStage(uint8) (runs: 259, μ: 14776, ~: 5846) +ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 15259, ~: 7408) +ReentrancyTest:test_stage_default() (gas: 2392) +ReentrancyTest:test_stage_enum_level1() (gas: 247) +ReentrancyTest:test_stage_enum_level2() (gas: 224) +ReentrancyTest:test_stage_enum_unset() (gas: 235) +SellTest:test_sell_arbitrum(uint32) (runs: 259, μ: 3900420, ~: 3900420) +SellTest:test_sell_base(uint32) (runs: 259, μ: 3801860, ~: 3801860) +SwapForTest:test_swap_for_arbitrum() (gas: 3221074) +SwapForTest:test_swap_for_arbitrum_sepolia() (gas: 3201146) +SwapForTest:test_swap_for_base() (gas: 3221074) +SwapWithTest:test_swap_with_arbitrum() (gas: 3221073) +SwapWithTest:test_swap_with_arbitrum_sepolia() (gas: 3201124) +SwapWithTest:test_swap_with_base() (gas: 3221074) +UnwindTest:test_unwind_arbitrum_sepolia() (gas: 3201101) +UnwindTest:test_unwind_is_authorized() (gas: 3242256) +UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 259, μ: 3933008, ~: 3933008) +UnwrapTest:test_unwrap_base(uint32) (runs: 259, μ: 3824944, ~: 3824944) +WithdrawTest:test_withdraw_arbitrum() (gas: 4022484) +WithdrawTest:test_withdraw_base() (gas: 3845878) +WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 3242008) +WithdrawTest:test_withdraw_is_authorized_base() (gas: 3242018) +WrapTest:test_wrap_arbitrum(uint32) (runs: 258, μ: 3761617, ~: 3761617) +WrapTest:test_wrap_base(uint32) (runs: 258, μ: 3671198, ~: 3671198) +ZapInTest:test_zap_in_arbitrum(uint32) (runs: 258, μ: 3893736, ~: 3893736) +ZapInTest:test_zap_in_base(uint32) (runs: 258, μ: 3790698, ~: 3790698) +ZapOutTest:test_zap_out_arbitum(uint32) (runs: 258, μ: 3855115, ~: 3855115) +ZapOutTest:test_zap_out_base(uint32) (runs: 258, μ: 3757041, ~: 3757041) \ No newline at end of file diff --git a/lcov.info b/lcov.info index ca81419..46e3353 100644 --- a/lcov.info +++ b/lcov.info @@ -1,30 +1,26 @@ TN: SF:script/Deploy.s.sol -FN:13,Setup.deploySystem -FNDA:0,Setup.deploySystem -DA:27,0 -FN:48,DeployBase.run +FN:13,Deploy.broadcast +FNDA:0,Deploy.broadcast +DA:14,0 +DA:15,0 +DA:17,0 +FN:20,Deploy.deploySystem +FNDA:0,Deploy.deploySystem +DA:34,3636 +FN:55,DeployBase.run FNDA:0,DeployBase.run -DA:49,0 -DA:50,0 -DA:52,0 -DA:64,0 -FN:76,DeployArbitrum.run +DA:56,0 +FN:78,DeployArbitrum.run FNDA:0,DeployArbitrum.run -DA:77,0 -DA:78,0 -DA:80,0 -DA:92,0 -FN:104,DeployArbitrumSepolia.run +DA:79,0 +FN:101,DeployArbitrumSepolia.run FNDA:0,DeployArbitrumSepolia.run -DA:105,0 -DA:106,0 -DA:108,0 -DA:120,0 -FNF:4 +DA:102,0 +FNF:5 FNH:0 -LF:13 -LH:0 +LF:7 +LH:1 BRF:0 BRH:0 end_of_record @@ -41,209 +37,213 @@ DA:65,3636 DA:66,3636 DA:67,3636 DA:68,3636 -DA:69,3636 -DA:70,3636 -DA:73,3636 -DA:74,3636 +DA:71,3636 +DA:72,3636 +DA:75,3636 +DA:76,3636 DA:77,3636 -DA:78,3636 -FN:87,Zap.isAuthorized -FNDA:2,Zap.isAuthorized -DA:88,2 -DA:91,2 -BRDA:91,0,0,2 -BRDA:91,0,1,2 -FN:96,Zap.onlyAave -FNDA:528,Zap.onlyAave -DA:97,528 -BRDA:97,1,0,528 -BRDA:97,1,1,- -FN:111,Zap.zapIn -FNDA:518,Zap.zapIn -DA:119,518 -DA:120,518 -DA:121,518 -FN:126,Zap._zapIn -FNDA:518,Zap._zapIn -DA:133,518 -DA:134,518 -FN:143,Zap.zapOut +FN:86,Zap.isAuthorized +FNDA:4,Zap.isAuthorized +DA:87,4 +DA:90,4 +BRDA:90,0,0,1 +BRDA:90,0,1,- +FN:95,Zap.onlyAave +FNDA:264,Zap.onlyAave +DA:96,264 +BRDA:96,1,0,264 +BRDA:96,1,1,- +FN:110,Zap.zapIn +FNDA:516,Zap.zapIn +DA:118,516 +DA:119,516 +DA:120,516 +FN:125,Zap._zapIn +FNDA:516,Zap._zapIn +DA:132,516 +DA:133,516 +FN:142,Zap.zapOut FNDA:516,Zap.zapOut +DA:150,516 DA:151,516 DA:152,516 -DA:153,516 -FN:158,Zap._zapOut +FN:157,Zap._zapOut FNDA:516,Zap._zapOut +DA:164,516 DA:165,516 -DA:166,516 -FN:182,Zap.wrap +FN:181,Zap.wrap FNDA:1032,Zap.wrap +DA:191,1032 DA:192,1032 DA:193,1032 -DA:194,1032 -FN:199,Zap._wrap -FNDA:1550,Zap._wrap -DA:208,1550 -DA:209,1550 -DA:213,1550 -DA:214,1550 +FN:198,Zap._wrap +FNDA:1548,Zap._wrap +DA:207,1548 +DA:208,1548 +DA:212,1548 +DA:213,1548 +DA:214,0 DA:215,0 -DA:216,0 -FN:229,Zap.unwrap +FN:228,Zap.unwrap FNDA:516,Zap.unwrap +DA:238,516 DA:239,516 DA:240,516 DA:241,516 -DA:242,516 -FN:247,Zap._unwrap +FN:246,Zap._unwrap FNDA:1032,Zap._unwrap +DA:254,1032 DA:255,1032 DA:256,1032 -DA:257,1032 +DA:260,1032 DA:261,1032 -DA:262,1032 +DA:262,0 DA:263,0 -DA:264,0 -FN:279,Zap.buy +FN:278,Zap.buy FNDA:1032,Zap.buy +DA:287,1032 DA:288,1032 DA:289,1032 -DA:290,1032 -FN:295,Zap._buy +FN:294,Zap._buy FNDA:1548,Zap._buy +DA:302,1548 DA:303,1548 -DA:304,1548 +DA:308,1548 DA:309,1548 DA:310,1548 -DA:311,1548 +DA:311,0 DA:312,0 -DA:313,0 -FN:324,Zap.sell +FN:323,Zap.sell FNDA:516,Zap.sell +DA:332,516 DA:333,516 DA:334,516 DA:335,516 -DA:336,516 -FN:341,Zap._sell -FNDA:1034,Zap._sell -DA:349,1034 -DA:350,1034 -DA:351,1034 -DA:356,1034 -DA:357,1034 +FN:340,Zap._sell +FNDA:1032,Zap._sell +DA:348,1032 +DA:349,1032 +DA:350,1032 +DA:355,1032 +DA:356,1032 +DA:357,0 DA:358,0 -DA:359,0 FN:377,Zap.unwind -FNDA:2,Zap.unwind -DA:390,0 -DA:392,0 -DA:403,0 +FNDA:1,Zap.unwind +DA:391,0 +DA:393,0 DA:405,0 -DA:413,0 -FN:423,Zap.executeOperation -FNDA:528,Zap.executeOperation -DA:435,0 +DA:407,0 +DA:415,0 +FN:425,Zap.executeOperation +FNDA:264,Zap.executeOperation DA:437,0 -DA:442,0 -DA:443,0 -DA:445,0 -DA:447,0 -FN:456,Zap._unwind +DA:439,0 +DA:453,0 +DA:455,0 +DA:457,0 +FN:465,Zap._unwind FNDA:0,Zap._unwind -DA:465,0 -DA:472,0 -DA:478,0 -DA:482,0 -DA:486,0 -DA:490,0 -DA:494,0 +DA:474,0 +DA:483,0 DA:498,0 -DA:505,0 -FN:513,Zap._approximateLoanNeeded -FNDA:0,Zap._approximateLoanNeeded -DA:519,0 +DA:502,0 +DA:506,0 +DA:510,0 +DA:514,0 DA:521,0 -DA:522,0 -DA:527,0 -DA:532,0 -FN:545,Zap.burn -FNDA:0,Zap.burn -DA:546,0 -DA:547,0 +FN:529,Zap._approximateLoanNeeded +FNDA:0,Zap._approximateLoanNeeded +DA:535,0 +DA:537,0 +DA:538,0 +DA:543,0 DA:548,0 -DA:549,0 -FN:554,Zap._burn +FN:561,Zap.burn +FNDA:0,Zap.burn +DA:562,0 +DA:563,0 +DA:564,0 +DA:565,0 +BRDA:565,2,0,- +FN:570,Zap._burn FNDA:0,Zap._burn -DA:555,0 -DA:556,0 -FN:569,Zap.withdraw +DA:571,0 +DA:572,0 +FN:585,Zap.withdraw FNDA:4,Zap.withdraw -DA:578,2 -DA:579,2 -DA:582,2 -FN:588,Zap._withdraw -FNDA:2,Zap._withdraw +DA:594,2 DA:595,2 -DA:596,2 -DA:601,2 -FN:616,Zap.swapFor -FNDA:0,Zap.swapFor -DA:625,0 -DA:626,0 -DA:627,0 -DA:629,0 -BRDA:629,2,0,- -DA:630,0 -FN:636,Zap._swapFor -FNDA:0,Zap._swapFor -DA:644,0 -DA:646,0 -DA:657,0 -DA:659,0 -DA:660,0 -DA:661,0 -DA:662,0 -FN:674,Zap.swapWith -FNDA:0,Zap.swapWith -DA:683,0 -DA:684,0 +DA:598,2 +FN:604,Zap._withdraw +FNDA:2,Zap._withdraw +DA:611,2 +DA:612,2 +DA:617,2 +FN:636,Zap.quoteSwapFor +FNDA:0,Zap.quoteSwapFor +DA:651,0 +FN:670,Zap.quoteSwapWith +FNDA:0,Zap.quoteSwapWith DA:685,0 -FN:690,Zap._swapWith -FNDA:0,Zap._swapWith -DA:698,0 -DA:700,0 +FN:700,Zap.swapFor +FNDA:0,Zap.swapFor +DA:709,0 +DA:710,0 DA:711,0 DA:713,0 +BRDA:713,3,0,- DA:714,0 -DA:715,0 -DA:716,0 -FN:729,Zap._pull -FNDA:4130,Zap._pull -DA:737,4130 -DA:739,4130 -DA:741,4130 -DA:742,4130 -DA:743,4130 -BRDA:743,3,0,- -BRDA:743,3,1,4130 -DA:747,0 -DA:748,0 -FN:757,Zap._push -FNDA:4132,Zap._push -DA:765,4132 -DA:767,4132 -DA:768,4132 -DA:769,4132 -BRDA:769,4,0,- -BRDA:769,4,1,4132 -DA:773,0 -DA:774,0 -FNF:29 +FN:720,Zap._swapFor +FNDA:0,Zap._swapFor +DA:728,0 +DA:730,0 +DA:741,0 +DA:742,0 +DA:743,0 +DA:744,0 +DA:745,0 +FN:757,Zap.swapWith +FNDA:0,Zap.swapWith +DA:766,0 +DA:767,0 +DA:768,0 +FN:773,Zap._swapWith +FNDA:0,Zap._swapWith +DA:781,0 +DA:783,0 +DA:794,0 +DA:795,0 +DA:796,0 +DA:797,0 +DA:798,0 +FN:811,Zap._pull +FNDA:4128,Zap._pull +DA:819,4128 +DA:821,4128 +DA:823,4128 +DA:824,4128 +DA:825,4128 +BRDA:825,4,0,- +BRDA:825,4,1,4128 +DA:829,0 +DA:830,0 +FN:839,Zap._push +FNDA:4130,Zap._push +DA:847,4130 +DA:849,4130 +DA:850,4130 +DA:851,4130 +BRDA:851,5,0,- +BRDA:851,5,1,4130 +DA:855,0 +DA:856,0 +FNF:31 FNH:21 -LF:141 -LH:76 -BRF:9 -BRH:5 +LF:140 +LH:75 +BRF:10 +BRH:4 end_of_record TN: SF:src/utils/Reentrancy.sol @@ -264,56 +264,55 @@ BRH:2 end_of_record TN: SF:test/utils/Bootstrap.sol -FN:35,Bootstrap.setUp -FNDA:33,Bootstrap.setUp -DA:36,33 -DA:37,33 -DA:39,33 -DA:40,33 -DA:41,33 -FN:44,Bootstrap.base -FNDA:1817,Bootstrap.base -DA:45,1817 -DA:46,1817 -DA:57,1817 -DA:58,1817 -DA:59,1817 -DA:60,1817 -DA:61,1817 -DA:62,1817 -DA:63,1817 -FN:67,Bootstrap.arbitrum -FNDA:1817,Bootstrap.arbitrum -DA:68,1817 -DA:69,1817 -DA:80,1817 -DA:81,1817 -DA:82,1817 -DA:83,1817 -DA:84,1817 -DA:85,1817 -DA:86,1817 -FN:90,Bootstrap.arbitrum_b -FNDA:0,Bootstrap.arbitrum_b -DA:91,0 -DA:92,0 -DA:103,0 -DA:104,0 -DA:105,0 -DA:106,0 -DA:107,0 -DA:108,0 -DA:109,0 -FN:113,Bootstrap._spin -FNDA:3100,Bootstrap._spin -DA:121,3100 -DA:122,3100 -DA:123,3100 -DA:124,3100 +FN:38,Bootstrap.setUp +FNDA:32,Bootstrap.setUp +DA:39,32 +DA:40,32 +DA:41,32 +DA:42,32 +DA:44,32 +DA:45,32 +DA:46,32 +FN:50,Bootstrap.base +FNDA:1551,Bootstrap.base +DA:52,1551 +DA:55,1551 +DA:68,1551 +DA:69,1551 +DA:70,1551 +DA:71,1551 +DA:72,1551 +DA:73,1551 +FN:78,Bootstrap.arbitrum +FNDA:1816,Bootstrap.arbitrum +DA:80,1816 +DA:83,1816 +DA:96,1816 +DA:97,1816 +DA:98,1816 +DA:99,1816 +DA:100,1816 +DA:101,1816 +FN:106,Bootstrap.arbitrum_sepolia +FNDA:267,Bootstrap.arbitrum_sepolia +DA:108,267 +DA:111,267 +DA:124,267 +DA:125,267 +DA:126,267 +DA:127,267 +DA:128,267 +DA:129,267 +FN:140,Bootstrap._spin +FNDA:3098,Bootstrap._spin +DA:148,3098 +DA:149,3098 +DA:150,3098 +DA:151,3098 FNF:5 -FNH:4 -LF:36 -LH:27 +FNH:5 +LF:35 +LH:35 BRF:0 BRH:0 end_of_record From 1f76342e71dbbf416b93d17d9aadfa09f0bd8fec Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 14:28:14 -0400 Subject: [PATCH 087/129] m-3.1 --- src/Errors.sol | 6 ++++++ src/Zap.sol | 16 +++++++++++----- src/interfaces/IERC20.sol | 20 -------------------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/Errors.sol b/src/Errors.sol index 7848b52..7865a38 100644 --- a/src/Errors.sol +++ b/src/Errors.sol @@ -31,4 +31,10 @@ contract Errors { /// @notice Unauthorized reentrant call. error ReentrancyGuardReentrantCall(); + /// @notice thrown when transferFrom fails + error PullFailed(bytes reason); + + /// @notice thrown when transfer fails + error PushFailed(bytes reason); + } diff --git a/src/Zap.sol b/src/Zap.sol index 61d420a..69bfba8 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -705,8 +705,11 @@ contract Zap is Enums, Errors { returns (bool) { IERC20 token = IERC20(_token); - token.safeTransferFrom(token, _from, address(this), _amount); - return true; + try token.transferFrom(_from, address(this), _amount) { + return true; + } catch (bytes memory reason) { + revert PullFailed(reason); + } } /// @dev push tokens to a receiver @@ -720,11 +723,14 @@ contract Zap is Enums, Errors { uint256 _amount ) internal - returns (bool success) + returns (bool) { IERC20 token = IERC20(_token); - token.safeTransfer(token, _receiver, _amount); - return true; + try token.transfer(_receiver, _amount) { + return true; + } catch (bytes memory reason) { + revert PushFailed(reason); + } } } diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 78fec06..8e50c0b 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -95,24 +95,4 @@ interface IERC20 { external returns (bool); - /** - * @dev Transfer `value` amount of `token` from the calling contract to - * `to`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeTransfer(IERC20 token, address to, uint256 value) external; - /** - * @dev Transfer `value` amount of `token` from `from` to `to`, spending the - * approval given by `from` to the - * calling contract. If `token` returns no value, non-reverting calls are - * assumed to be successful. - */ - function safeTransferFrom( - IERC20 token, - address from, - address to, - uint256 value - ) - external; - } From ab41a3b0639d0a29dfa0efffb98ace1b46e65a1f Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 14:47:20 -0400 Subject: [PATCH 088/129] removed revert if transfer does not return bool --- src/Zap.sol | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 02fe06d..0e8e546 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -388,7 +388,7 @@ contract Zap is Reentrancy, Errors { isAuthorized(_accountId) requireStage(Stage.UNSET) { - stage = Stage.LEVEL1;x + stage = Stage.LEVEL1; bytes memory params = abi.encode( _accountId, @@ -814,18 +814,14 @@ contract Zap is Reentrancy, Errors { uint256 _amount ) internal - returns (bool success) + returns (bool) { IERC20 token = IERC20(_token); try token.transferFrom(_from, address(this), _amount) returns ( bool result ) { - success = result; - require( - success, - PullFailed(abi.encodePacked(address(token), _from, _amount)) - ); + return result; } catch Error(string memory reason) { revert PullFailed(bytes(reason)); } @@ -847,11 +843,7 @@ contract Zap is Reentrancy, Errors { IERC20 token = IERC20(_token); try token.transfer(_receiver, _amount) returns (bool result) { - success = result; - require( - success, - PushFailed(abi.encodePacked(address(token), _receiver, _amount)) - ); + return result; } catch Error(string memory reason) { revert PushFailed(bytes(reason)); } From 40344866e1af3401ce3d7594ec4cd61d32bacb44 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 16:20:14 -0400 Subject: [PATCH 089/129] reset allowance to 0 --- src/Zap.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Zap.sol b/src/Zap.sol index fcae920..29578e6 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -570,6 +570,7 @@ contract Zap is Reentrancy, Errors { function _burn(uint256 _amount, uint128 _accountId) internal { IERC20(USDX).approve(PERPS_MARKET, _amount); IPerpsMarket(PERPS_MARKET).payDebt(_accountId, _amount); + IERC20(USDX).approve(PERPS_MARKET, 0); } /*////////////////////////////////////////////////////////////// @@ -744,6 +745,8 @@ contract Zap is Reentrancy, Errors { } catch Error(string memory reason) { revert SwapFailed(reason); } + + IERC20(_from).approve(ROUTER, 0); } /// @notice swap a specific amount of tokens for a tolerable amount of USDC From 262774dbf11a8f2a0d0655b42450393f0cf61e5c Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 16:38:53 -0400 Subject: [PATCH 090/129] I-1 --- src/Zap.sol | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 29578e6..6e178bb 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -205,15 +205,11 @@ contract Zap is Reentrancy, Errors { returns (uint256 wrapped) { IERC20(_token).approve(SPOT_MARKET, _amount); - try ISpotMarket(SPOT_MARKET).wrap({ + (wrapped, ) = ISpotMarket(SPOT_MARKET).wrap({ marketId: _synthId, wrapAmount: _amount, minAmountReceived: _tolerance - }) returns (uint256 amount, ISpotMarket.Data memory) { - wrapped = amount; - } catch Error(string memory reason) { - revert WrapFailed(reason); - } + }); } /// @notice unwrap collateral via synthetix spot market @@ -253,15 +249,11 @@ contract Zap is Reentrancy, Errors { { address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); IERC20(synth).approve(SPOT_MARKET, _amount); - try ISpotMarket(SPOT_MARKET).unwrap({ + (unwrapped, ) = ISpotMarket(SPOT_MARKET).unwrap({ marketId: _synthId, unwrapAmount: _amount, minAmountReceived: _tolerance - }) returns (uint256 amount, ISpotMarket.Data memory) { - unwrapped = amount; - } catch Error(string memory reason) { - revert UnwrapFailed(reason); - } + }); } /*////////////////////////////////////////////////////////////// @@ -300,17 +292,13 @@ contract Zap is Reentrancy, Errors { returns (uint256 received, address synth) { IERC20(USDX).approve(SPOT_MARKET, _amount); - try ISpotMarket(SPOT_MARKET).buy({ + (received, ) = ISpotMarket(SPOT_MARKET).buy({ marketId: _synthId, usdAmount: _amount, minAmountReceived: _tolerance, referrer: REFERRER - }) returns (uint256 amount, ISpotMarket.Data memory) { - received = amount; - synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); - } catch Error(string memory reason) { - revert BuyFailed(reason); - } + }); + synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); } /// @notice sell synth via synthetix spot market @@ -347,16 +335,12 @@ contract Zap is Reentrancy, Errors { { address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); IERC20(synth).approve(SPOT_MARKET, _amount); - try ISpotMarket(SPOT_MARKET).sell({ + (received,) = ISpotMarket(SPOT_MARKET).sell({ marketId: _synthId, synthAmount: _amount, minUsdAmount: _tolerance, referrer: REFERRER - }) returns (uint256 amount, ISpotMarket.Data memory) { - received = amount; - } catch Error(string memory reason) { - revert SellFailed(reason); - } + }); } /*////////////////////////////////////////////////////////////// From 715fb37ff3c2af6135504d9de1c05c78f768f854 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 16:40:57 -0400 Subject: [PATCH 091/129] removed accidental changes from L-3 --- src/Zap.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 6e178bb..7bf1e61 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -554,7 +554,6 @@ contract Zap is Reentrancy, Errors { function _burn(uint256 _amount, uint128 _accountId) internal { IERC20(USDX).approve(PERPS_MARKET, _amount); IPerpsMarket(PERPS_MARKET).payDebt(_accountId, _amount); - IERC20(USDX).approve(PERPS_MARKET, 0); } /*////////////////////////////////////////////////////////////// @@ -729,8 +728,6 @@ contract Zap is Reentrancy, Errors { } catch Error(string memory reason) { revert SwapFailed(reason); } - - IERC20(_from).approve(ROUTER, 0); } /// @notice swap a specific amount of tokens for a tolerable amount of USDC From 399ad1fa72e5c8515a4fcce66dfda670c990ffbe Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 16:41:22 -0400 Subject: [PATCH 092/129] fmt --- src/Zap.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 7bf1e61..c69a10e 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -205,7 +205,7 @@ contract Zap is Reentrancy, Errors { returns (uint256 wrapped) { IERC20(_token).approve(SPOT_MARKET, _amount); - (wrapped, ) = ISpotMarket(SPOT_MARKET).wrap({ + (wrapped,) = ISpotMarket(SPOT_MARKET).wrap({ marketId: _synthId, wrapAmount: _amount, minAmountReceived: _tolerance @@ -249,7 +249,7 @@ contract Zap is Reentrancy, Errors { { address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); IERC20(synth).approve(SPOT_MARKET, _amount); - (unwrapped, ) = ISpotMarket(SPOT_MARKET).unwrap({ + (unwrapped,) = ISpotMarket(SPOT_MARKET).unwrap({ marketId: _synthId, unwrapAmount: _amount, minAmountReceived: _tolerance @@ -292,7 +292,7 @@ contract Zap is Reentrancy, Errors { returns (uint256 received, address synth) { IERC20(USDX).approve(SPOT_MARKET, _amount); - (received, ) = ISpotMarket(SPOT_MARKET).buy({ + (received,) = ISpotMarket(SPOT_MARKET).buy({ marketId: _synthId, usdAmount: _amount, minAmountReceived: _tolerance, From 8f359df979aae6950932a9858ef20d76004122b3 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 17:08:12 -0400 Subject: [PATCH 093/129] zero address check --- src/Zap.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Zap.sol b/src/Zap.sol index fcae920..2bd1332 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -844,6 +844,7 @@ contract Zap is Reentrancy, Errors { internal returns (bool success) { + require(_receiver != address(0), PushFailed("Zero Address")); IERC20 token = IERC20(_token); try token.transfer(_receiver, _amount) returns (bool result) { From 274af46097001a6c1aa3feccd3686e663dbda9bd Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 17:13:12 -0400 Subject: [PATCH 094/129] moved synth from `_buy()` to `buy()` --- src/Zap.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index fcae920..7883b08 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -284,8 +284,9 @@ contract Zap is Reentrancy, Errors { external returns (uint256 received, address synth) { + synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); _pull(USDX, msg.sender, _amount); - (received, synth) = _buy(_synthId, _amount, _tolerance); + received = _buy(_synthId, _amount, _tolerance); _push(synth, _receiver, received); } @@ -297,7 +298,7 @@ contract Zap is Reentrancy, Errors { uint256 _tolerance ) internal - returns (uint256 received, address synth) + returns (uint256 received) { IERC20(USDX).approve(SPOT_MARKET, _amount); try ISpotMarket(SPOT_MARKET).buy({ @@ -307,7 +308,6 @@ contract Zap is Reentrancy, Errors { referrer: REFERRER }) returns (uint256 amount, ISpotMarket.Data memory) { received = amount; - synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); } catch Error(string memory reason) { revert BuyFailed(reason); } From 969e9c052947c3d9f5ba5701c699d7e0ccf2a3bd Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 3 Oct 2024 17:23:01 -0400 Subject: [PATCH 095/129] made the immutables that could be constants constants --- src/Zap.sol | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index fcae920..3328bd8 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -24,24 +24,27 @@ contract Zap is Reentrancy, Errors { address public immutable USDC; /// @custom:synthetix + bytes32 public constant MODIFY_PERMISSION = "PERPS_MODIFY_COLLATERAL"; + bytes32 public constant BURN_PERMISSION = "BURN"; + uint128 public constant USDX_ID = 0; + address public immutable USDX; address public immutable SPOT_MARKET; address public immutable PERPS_MARKET; address public immutable CORE; address public immutable REFERRER; uint128 public immutable SUSDC_SPOT_ID; - bytes32 public immutable MODIFY_PERMISSION; - bytes32 public immutable BURN_PERMISSION; - uint128 public immutable USDX_ID; /// @custom:aave + uint16 public constant REFERRAL_CODE = 0; + address public immutable AAVE; - uint16 public immutable REFERRAL_CODE; /// @custom:uniswap + uint24 public constant FEE_TIER = 3000; + address public immutable ROUTER; address public immutable QUOTER; - uint24 public immutable FEE_TIER; constructor( address _usdc, @@ -63,18 +66,13 @@ contract Zap is Reentrancy, Errors { PERPS_MARKET = _perpsMarket; REFERRER = _referrer; SUSDC_SPOT_ID = _susdcSpotId; - MODIFY_PERMISSION = "PERPS_MODIFY_COLLATERAL"; - BURN_PERMISSION = "BURN"; - USDX_ID = 0; /// @custom:aave AAVE = _aave; - REFERRAL_CODE = 0; /// @custom:uniswap ROUTER = _router; QUOTER = _quoter; - FEE_TIER = 3000; } /*////////////////////////////////////////////////////////////// From 82621e125d5ca33c65dd8625a58234db0a272257 Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 4 Oct 2024 15:23:40 -0400 Subject: [PATCH 096/129] =?UTF-8?q?=E2=9C=85=20test=20swap=20for?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Swap.for.t.sol | 107 +++++++++++++++++++++++++++++++++-- test/interfaces/IUniswap.sol | 21 +++++++ test/utils/Bootstrap.sol | 2 + 3 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 test/interfaces/IUniswap.sol diff --git a/test/Swap.for.t.sol b/test/Swap.for.t.sol index 10e7d57..bad6a04 100644 --- a/test/Swap.for.t.sol +++ b/test/Swap.for.t.sol @@ -5,8 +5,11 @@ import { Bootstrap, Constants, IERC20, + IFactory, + IFactory, IPerpsMarket, IPool, + IRouter, ISpotMarket, Test, Zap @@ -14,13 +17,109 @@ import { contract SwapForTest is Bootstrap { - /// @custom:todo - function test_swap_for_base() public base {} + function test_swap_for_base() public base { + uint256 amount = 100e6; + uint256 tolerance = type(uint256).max / 4; + _spin(ACTOR, weth, tolerance, address(zap)); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(weth.balanceOf(ACTOR), tolerance); + vm.startPrank(ACTOR); + zap.swapFor({ + _from: address(weth), + _amount: amount, + _tolerance: tolerance, + _receiver: ACTOR + }); + assertGt(usdc.balanceOf(ACTOR), 0); + assertLt(weth.balanceOf(ACTOR), tolerance); + vm.stopPrank(); + } - /// @custom:todo - function test_swap_for_arbitrum() public arbitrum {} + function test_swap_for_arbitrum(uint8 percentage) public arbitrum { + vm.assume(percentage < 95 && percentage > 0); + + uint256 tolerance = type(uint256).max; + _spin(ACTOR, weth, tolerance, address(zap)); + + address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( + address(weth), address(usdc), zap.FEE_TIER() + ); + uint256 depth = usdc.balanceOf(pool); + uint256 amount = depth * (percentage / 100); + + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(weth.balanceOf(ACTOR), tolerance); + + vm.startPrank(ACTOR); + + if (amount == 0) { + vm.expectRevert(); + } + + zap.swapFor({ + _from: address(weth), + _amount: amount, + _tolerance: tolerance, + _receiver: ACTOR + }); + + assertTrue( + amount > 1e6 + ? usdc.balanceOf(ACTOR) < 0 + : usdc.balanceOf(ACTOR) == 0 + ); + assertLe(weth.balanceOf(ACTOR), tolerance); + + vm.stopPrank(); + } /// @custom:todo function test_swap_for_arbitrum_sepolia() public arbitrum_sepolia {} + bytes32 internal constant POOL_INIT_CODE_HASH = + 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; + + struct PoolKey { + address token0; + address token1; + uint24 fee; + } + + function getPoolKey( + address tokenA, + address tokenB, + uint24 fee + ) + internal + pure + returns (PoolKey memory) + { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); + } + + function computeAddress( + address factory, + PoolKey memory key + ) + internal + pure + returns (address pool) + { + require(key.token0 < key.token1); + uint256 pool256 = uint256( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encode(key.token0, key.token1, key.fee)), + POOL_INIT_CODE_HASH + ) + ) + ); + assembly { + pool := shr(96, pool256) + } + } + } diff --git a/test/interfaces/IUniswap.sol b/test/interfaces/IUniswap.sol new file mode 100644 index 0000000..150cf9f --- /dev/null +++ b/test/interfaces/IUniswap.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +interface IRouter { + + function factory() external view returns (address); + +} + +interface IFactory { + + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) + external + view + returns (address pool); + +} diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol index d2d27a2..5c03c69 100644 --- a/test/utils/Bootstrap.sol +++ b/test/utils/Bootstrap.sol @@ -7,6 +7,8 @@ import { } from "../../script/utils/Parameters.sol"; import {Errors, IERC20, IPool, Reentrancy, Zap} from "../../src/Zap.sol"; import {IPerpsMarket, ISpotMarket} from "../interfaces/ISynthetix.sol"; + +import {IFactory, IRouter} from "../interfaces/IUniswap.sol"; import {Constants} from "../utils/Constants.sol"; import {Test} from "forge-std/Test.sol"; From ac89351e6f880e7f929181c4476eae5d9fe28cba Mon Sep 17 00:00:00 2001 From: jaredborders Date: Fri, 4 Oct 2024 15:24:20 -0400 Subject: [PATCH 097/129] =?UTF-8?q?=F0=9F=93=B8=20updat=20lcov/gas-snapsho?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 16 ++--- lcov.info | 178 +++++++++++++++++++++++++------------------------- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index a8905ed..2469eef 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,8 +1,8 @@ -AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 264, μ: 3225005, ~: 3225004) -BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 264, μ: 3201214, ~: 3201214) -BuyTest:test_buy_arbitrum(uint32) (runs: 258, μ: 3737678, ~: 3737678) -BuyTest:test_buy_base(uint32) (runs: 258, μ: 3657317, ~: 3657317) -ReentrancyTest:test_requireStage(uint8) (runs: 259, μ: 14776, ~: 5846) +AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 269, μ: 3225005, ~: 3225004) +BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 269, μ: 3201214, ~: 3201214) +BuyTest:test_buy_arbitrum(uint32) (runs: 259, μ: 3737678, ~: 3737678) +BuyTest:test_buy_base(uint32) (runs: 259, μ: 3657317, ~: 3657317) +ReentrancyTest:test_requireStage(uint8) (runs: 261, μ: 14784, ~: 5846) ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 15259, ~: 7408) ReentrancyTest:test_stage_default() (gas: 2392) ReentrancyTest:test_stage_enum_level1() (gas: 247) @@ -10,9 +10,9 @@ ReentrancyTest:test_stage_enum_level2() (gas: 224) ReentrancyTest:test_stage_enum_unset() (gas: 235) SellTest:test_sell_arbitrum(uint32) (runs: 259, μ: 3900420, ~: 3900420) SellTest:test_sell_base(uint32) (runs: 259, μ: 3801860, ~: 3801860) -SwapForTest:test_swap_for_arbitrum() (gas: 3221074) -SwapForTest:test_swap_for_arbitrum_sepolia() (gas: 3201146) -SwapForTest:test_swap_for_base() (gas: 3221074) +SwapForTest:test_swap_for_arbitrum(uint8) (runs: 263, μ: 4092489, ~: 4092489) +SwapForTest:test_swap_for_arbitrum_sepolia() (gas: 3785536) +SwapForTest:test_swap_for_base() (gas: 4127716) SwapWithTest:test_swap_with_arbitrum() (gas: 3221073) SwapWithTest:test_swap_with_arbitrum_sepolia() (gas: 3201124) SwapWithTest:test_swap_with_base() (gas: 3221074) diff --git a/lcov.info b/lcov.info index 46e3353..94768c3 100644 --- a/lcov.info +++ b/lcov.info @@ -7,7 +7,7 @@ DA:15,0 DA:17,0 FN:20,Deploy.deploySystem FNDA:0,Deploy.deploySystem -DA:34,3636 +DA:34,3908 FN:55,DeployBase.run FNDA:0,DeployBase.run DA:56,0 @@ -27,21 +27,21 @@ end_of_record TN: SF:src/Zap.sol FN:46,Zap. -FNDA:3636,Zap. -DA:58,3636 -DA:61,3636 -DA:62,3636 -DA:63,3636 -DA:64,3636 -DA:65,3636 -DA:66,3636 -DA:67,3636 -DA:68,3636 -DA:71,3636 -DA:72,3636 -DA:75,3636 -DA:76,3636 -DA:77,3636 +FNDA:3908,Zap. +DA:58,3908 +DA:61,3908 +DA:62,3908 +DA:63,3908 +DA:64,3908 +DA:65,3908 +DA:66,3908 +DA:67,3908 +DA:68,3908 +DA:71,3908 +DA:72,3908 +DA:75,3908 +DA:76,3908 +DA:77,3908 FN:86,Zap.isAuthorized FNDA:4,Zap.isAuthorized DA:87,4 @@ -49,9 +49,9 @@ DA:90,4 BRDA:90,0,0,1 BRDA:90,0,1,- FN:95,Zap.onlyAave -FNDA:264,Zap.onlyAave -DA:96,264 -BRDA:96,1,0,264 +FNDA:269,Zap.onlyAave +DA:96,269 +BRDA:96,1,0,269 BRDA:96,1,1,- FN:110,Zap.zapIn FNDA:516,Zap.zapIn @@ -136,7 +136,7 @@ DA:405,0 DA:407,0 DA:415,0 FN:425,Zap.executeOperation -FNDA:264,Zap.executeOperation +FNDA:269,Zap.executeOperation DA:437,0 DA:439,0 DA:453,0 @@ -187,22 +187,22 @@ FN:670,Zap.quoteSwapWith FNDA:0,Zap.quoteSwapWith DA:685,0 FN:700,Zap.swapFor -FNDA:0,Zap.swapFor -DA:709,0 -DA:710,0 -DA:711,0 -DA:713,0 -BRDA:713,3,0,- -DA:714,0 +FNDA:264,Zap.swapFor +DA:709,264 +DA:710,264 +DA:711,1 +DA:713,1 +BRDA:713,3,0,1 +DA:714,1 FN:720,Zap._swapFor -FNDA:0,Zap._swapFor -DA:728,0 -DA:730,0 -DA:741,0 -DA:742,0 -DA:743,0 -DA:744,0 -DA:745,0 +FNDA:264,Zap._swapFor +DA:728,264 +DA:730,264 +DA:741,264 +DA:742,1 +DA:743,1 +DA:744,263 +DA:745,263 FN:757,Zap.swapWith FNDA:0,Zap.swapWith DA:766,0 @@ -218,32 +218,32 @@ DA:796,0 DA:797,0 DA:798,0 FN:811,Zap._pull -FNDA:4128,Zap._pull -DA:819,4128 -DA:821,4128 -DA:823,4128 -DA:824,4128 -DA:825,4128 +FNDA:4392,Zap._pull +DA:819,4392 +DA:821,4392 +DA:823,4392 +DA:824,4392 +DA:825,4392 BRDA:825,4,0,- -BRDA:825,4,1,4128 +BRDA:825,4,1,4392 DA:829,0 DA:830,0 FN:839,Zap._push -FNDA:4130,Zap._push -DA:847,4130 -DA:849,4130 -DA:850,4130 -DA:851,4130 +FNDA:4132,Zap._push +DA:847,4132 +DA:849,4132 +DA:850,4132 +DA:851,4132 BRDA:851,5,0,- -BRDA:851,5,1,4130 +BRDA:851,5,1,4132 DA:855,0 DA:856,0 FNF:31 -FNH:21 +FNH:23 LF:140 -LH:75 +LH:87 BRF:10 -BRH:4 +BRH:5 end_of_record TN: SF:src/utils/Reentrancy.sol @@ -251,10 +251,10 @@ FN:27,Reentrancy.requireStage FNDA:0,Reentrancy.requireStage DA:28,0 FN:32,Reentrancy._requireStage -FNDA:427,Reentrancy._requireStage -DA:33,427 +FNDA:429,Reentrancy._requireStage +DA:33,429 BRDA:33,0,0,168 -BRDA:33,0,1,259 +BRDA:33,0,1,261 FNF:2 FNH:1 LF:2 @@ -264,51 +264,51 @@ BRH:2 end_of_record TN: SF:test/utils/Bootstrap.sol -FN:38,Bootstrap.setUp +FN:40,Bootstrap.setUp FNDA:32,Bootstrap.setUp -DA:39,32 -DA:40,32 DA:41,32 DA:42,32 +DA:43,32 DA:44,32 -DA:45,32 DA:46,32 -FN:50,Bootstrap.base +DA:47,32 +DA:48,32 +FN:52,Bootstrap.base FNDA:1551,Bootstrap.base -DA:52,1551 -DA:55,1551 -DA:68,1551 -DA:69,1551 +DA:54,1551 +DA:57,1551 DA:70,1551 DA:71,1551 DA:72,1551 DA:73,1551 -FN:78,Bootstrap.arbitrum -FNDA:1816,Bootstrap.arbitrum -DA:80,1816 -DA:83,1816 -DA:96,1816 -DA:97,1816 -DA:98,1816 -DA:99,1816 -DA:100,1816 -DA:101,1816 -FN:106,Bootstrap.arbitrum_sepolia -FNDA:267,Bootstrap.arbitrum_sepolia -DA:108,267 -DA:111,267 -DA:124,267 -DA:125,267 -DA:126,267 -DA:127,267 -DA:128,267 -DA:129,267 -FN:140,Bootstrap._spin -FNDA:3098,Bootstrap._spin -DA:148,3098 -DA:149,3098 -DA:150,3098 -DA:151,3098 +DA:74,1551 +DA:75,1551 +FN:80,Bootstrap.arbitrum +FNDA:2083,Bootstrap.arbitrum +DA:82,2083 +DA:85,2083 +DA:98,2083 +DA:99,2083 +DA:100,2083 +DA:101,2083 +DA:102,2083 +DA:103,2083 +FN:108,Bootstrap.arbitrum_sepolia +FNDA:272,Bootstrap.arbitrum_sepolia +DA:110,272 +DA:113,272 +DA:126,272 +DA:127,272 +DA:128,272 +DA:129,272 +DA:130,272 +DA:131,272 +FN:142,Bootstrap._spin +FNDA:3362,Bootstrap._spin +DA:150,3362 +DA:151,3362 +DA:152,3362 +DA:153,3362 FNF:5 FNH:5 LF:35 From 031eb79875a73600205ef6d47a90d30902aec485 Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 9 Oct 2024 15:40:24 -0400 Subject: [PATCH 098/129] Revert "removed accidental changes from L-3" This reverts commit 715fb37ff3c2af6135504d9de1c05c78f768f854. --- src/Zap.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Zap.sol b/src/Zap.sol index 3afd9dd..cbaeac2 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -556,6 +556,7 @@ contract Zap is Reentrancy, Errors { function _burn(uint256 _amount, uint128 _accountId) internal { IERC20(USDX).approve(PERPS_MARKET, _amount); IPerpsMarket(PERPS_MARKET).payDebt(_accountId, _amount); + IERC20(USDX).approve(PERPS_MARKET, 0); } /*////////////////////////////////////////////////////////////// @@ -730,6 +731,8 @@ contract Zap is Reentrancy, Errors { } catch Error(string memory reason) { revert SwapFailed(reason); } + + IERC20(_from).approve(ROUTER, 0); } /// @notice swap a specific amount of tokens for a tolerable amount of USDC From aad0bea14a0348bc54d57ca54cb001e746a9f01c Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 9 Oct 2024 12:59:04 -0700 Subject: [PATCH 099/129] =?UTF-8?q?=E2=9C=85=20allowance=20test=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 2 +- src/interfaces/IERC20.sol | 8 ++++++++ test/Swap.for.t.sol | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Zap.sol b/src/Zap.sol index cbaeac2..34b2667 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -159,7 +159,7 @@ contract Zap is Reentrancy, Errors { internal returns (uint256 zapped) { - (zapped,) = _buy(SUSDC_SPOT_ID, _amount, _tolerance); + zapped = _buy(SUSDC_SPOT_ID, _amount, _tolerance); zapped = _unwrap(SUSDC_SPOT_ID, zapped, _tolerance); } diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 7b01ebf..9e55e7b 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -7,6 +7,14 @@ interface IERC20 { function balanceOf(address account) external view returns (uint256); + function allowance( + address owner, + address spender + ) + external + view + returns (uint256); + function transfer(address to, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); diff --git a/test/Swap.for.t.sol b/test/Swap.for.t.sol index bad6a04..a6ee7c4 100644 --- a/test/Swap.for.t.sol +++ b/test/Swap.for.t.sol @@ -32,6 +32,7 @@ contract SwapForTest is Bootstrap { }); assertGt(usdc.balanceOf(ACTOR), 0); assertLt(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); vm.stopPrank(); } @@ -69,6 +70,7 @@ contract SwapForTest is Bootstrap { : usdc.balanceOf(ACTOR) == 0 ); assertLe(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); vm.stopPrank(); } From bb84bb427168fd122054502bc44606cbd30a5a0d Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:29:21 -0500 Subject: [PATCH 100/129] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20remove=20assign?= =?UTF-8?q?ment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Zap.sol b/src/Zap.sol index 34b2667..e52fd42 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -26,7 +26,7 @@ contract Zap is Reentrancy, Errors { /// @custom:synthetix bytes32 public constant MODIFY_PERMISSION = "PERPS_MODIFY_COLLATERAL"; bytes32 public constant BURN_PERMISSION = "BURN"; - uint128 public constant USDX_ID = 0; + uint128 public constant USDX_ID; address public immutable USDX; address public immutable SPOT_MARKET; From 6318d0c91236a840da38729137086415a580ed1b Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Wed, 9 Oct 2024 21:29:19 -0500 Subject: [PATCH 101/129] =?UTF-8?q?=F0=9F=91=B7add=20flush?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 13 +++++++------ src/utils/Flush.sol | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 src/utils/Flush.sol diff --git a/src/Zap.sol b/src/Zap.sol index e52fd42..c2f0c71 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -26,23 +26,19 @@ contract Zap is Reentrancy, Errors { /// @custom:synthetix bytes32 public constant MODIFY_PERMISSION = "PERPS_MODIFY_COLLATERAL"; bytes32 public constant BURN_PERMISSION = "BURN"; - uint128 public constant USDX_ID; - + uint128 public immutable USDX_ID; address public immutable USDX; address public immutable SPOT_MARKET; address public immutable PERPS_MARKET; - address public immutable CORE; address public immutable REFERRER; uint128 public immutable SUSDC_SPOT_ID; /// @custom:aave uint16 public constant REFERRAL_CODE = 0; - address public immutable AAVE; /// @custom:uniswap uint24 public constant FEE_TIER = 3000; - address public immutable ROUTER; address public immutable QUOTER; @@ -480,13 +476,18 @@ contract Zap is Reentrancy, Errors { ) ); - // zap USDC from flashloan into USDx + // zap USDC from flashloan into USDx; + // ALL USDC flashloaned from Aave is zapped into USDx uint256 usdxAmount = _zapIn(_flashloan, _zapTolerance); // burn USDx to pay off synthetix perp position debt; // debt is denominated in USD and thus repaid with USDx _burn(usdxAmount, _accountId); + /// @dev given the USDC buffer, an amount of USDx + /// necessarily less than the buffer will remain (<$1); + /// this amount is captured by the protocol + // withdraw synthetix perp position collateral to this contract; // i.e., # of sETH, # of sUSDe, # of sUSDC (...) _withdraw(_collateralId, _collateralAmount, _accountId); diff --git a/src/utils/Flush.sol b/src/utils/Flush.sol new file mode 100644 index 0000000..46267fb --- /dev/null +++ b/src/utils/Flush.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {IERC20} from "../interfaces/IERC20.sol"; + +/// @title token flushing utility +/// @author @jaredborders +contract Flush { + + /// @custom:plumber + address public PLUMBER; + + /// @notice thrown when caller is not the plumber + error OnlyPlumber(); + + constructor(address _plumber) { + PLUMBER = _plumber; + } + + /// @notice flush dust out of the contract + /// @custom:plumber is the only authorized caller + /// @param _token address of token to flush + function flush(address _token) external { + require(msg.sender == PLUMBER, OnlyPlumber()); + IERC20 token = IERC20(_token); + uint256 balance = token.balanceOf(address(this)); + if (balance > 0) token.transfer(msg.sender, balance); + } + + /// @notice designate a new plumber + /// @custom:plumber is the only authorized caller + /// @dev zero address can be used to remove flush capability + /// @param _newPlumber address of new plumber + function designatePlumber(address _newPlumber) external { + require(msg.sender == PLUMBER, OnlyPlumber()); + PLUMBER = _newPlumber; + } + +} From 843dd3462eefe605c329b1037bf01d83b05f4eb4 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Wed, 9 Oct 2024 21:29:30 -0500 Subject: [PATCH 102/129] =?UTF-8?q?=E2=9C=85=20add=20flush=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Flush.t.sol | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/Flush.t.sol diff --git a/test/Flush.t.sol b/test/Flush.t.sol new file mode 100644 index 0000000..37b5d08 --- /dev/null +++ b/test/Flush.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract FlushTest is Bootstrap { + + /// @custom:todo + function test_flush(uint32 amount) public arbitrum_sepolia {} + +} From c693ae2805eaf66d72b7d9b84f3339a0afb0bdbb Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Wed, 9 Oct 2024 21:30:43 -0500 Subject: [PATCH 103/129] =?UTF-8?q?=F0=9F=93=B8=20update=20snapshot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 57 +++--- lcov.info | 501 +++++++++++++++++++++++++------------------------- 2 files changed, 284 insertions(+), 274 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 2469eef..c1d4674 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,32 +1,33 @@ -AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 269, μ: 3225005, ~: 3225004) -BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 269, μ: 3201214, ~: 3201214) -BuyTest:test_buy_arbitrum(uint32) (runs: 259, μ: 3737678, ~: 3737678) -BuyTest:test_buy_base(uint32) (runs: 259, μ: 3657317, ~: 3657317) -ReentrancyTest:test_requireStage(uint8) (runs: 261, μ: 14784, ~: 5846) -ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 15259, ~: 7408) +AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 256, μ: 3154608, ~: 3154607) +BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 256, μ: 3130817, ~: 3130817) +BuyTest:test_buy_arbitrum(uint32) (runs: 256, μ: 3666974, ~: 3666974) +BuyTest:test_buy_base(uint32) (runs: 256, μ: 3586613, ~: 3586613) +FlushTest:test_flush(uint32) (runs: 256, μ: 3130839, ~: 3130839) +ReentrancyTest:test_requireStage(uint8) (runs: 256, μ: 15037, ~: 5846) +ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 15266, ~: 7408) ReentrancyTest:test_stage_default() (gas: 2392) ReentrancyTest:test_stage_enum_level1() (gas: 247) ReentrancyTest:test_stage_enum_level2() (gas: 224) ReentrancyTest:test_stage_enum_unset() (gas: 235) -SellTest:test_sell_arbitrum(uint32) (runs: 259, μ: 3900420, ~: 3900420) -SellTest:test_sell_base(uint32) (runs: 259, μ: 3801860, ~: 3801860) -SwapForTest:test_swap_for_arbitrum(uint8) (runs: 263, μ: 4092489, ~: 4092489) -SwapForTest:test_swap_for_arbitrum_sepolia() (gas: 3785536) -SwapForTest:test_swap_for_base() (gas: 4127716) -SwapWithTest:test_swap_with_arbitrum() (gas: 3221073) -SwapWithTest:test_swap_with_arbitrum_sepolia() (gas: 3201124) -SwapWithTest:test_swap_with_base() (gas: 3221074) -UnwindTest:test_unwind_arbitrum_sepolia() (gas: 3201101) -UnwindTest:test_unwind_is_authorized() (gas: 3242256) -UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 259, μ: 3933008, ~: 3933008) -UnwrapTest:test_unwrap_base(uint32) (runs: 259, μ: 3824944, ~: 3824944) -WithdrawTest:test_withdraw_arbitrum() (gas: 4022484) -WithdrawTest:test_withdraw_base() (gas: 3845878) -WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 3242008) -WithdrawTest:test_withdraw_is_authorized_base() (gas: 3242018) -WrapTest:test_wrap_arbitrum(uint32) (runs: 258, μ: 3761617, ~: 3761617) -WrapTest:test_wrap_base(uint32) (runs: 258, μ: 3671198, ~: 3671198) -ZapInTest:test_zap_in_arbitrum(uint32) (runs: 258, μ: 3893736, ~: 3893736) -ZapInTest:test_zap_in_base(uint32) (runs: 258, μ: 3790698, ~: 3790698) -ZapOutTest:test_zap_out_arbitum(uint32) (runs: 258, μ: 3855115, ~: 3855115) -ZapOutTest:test_zap_out_base(uint32) (runs: 258, μ: 3757041, ~: 3757041) \ No newline at end of file +SellTest:test_sell_arbitrum(uint32) (runs: 256, μ: 3829418, ~: 3829418) +SellTest:test_sell_base(uint32) (runs: 256, μ: 3730858, ~: 3730858) +SwapForTest:test_swap_for_arbitrum(uint8) (runs: 256, μ: 3378814, ~: 3378814) +SwapForTest:test_swap_for_arbitrum_sepolia() (gas: 3130749) +SwapForTest:test_swap_for_base() (gas: 3418014) +SwapWithTest:test_swap_with_arbitrum() (gas: 3150676) +SwapWithTest:test_swap_with_arbitrum_sepolia() (gas: 3130727) +SwapWithTest:test_swap_with_base() (gas: 3150677) +UnwindTest:test_unwind_arbitrum_sepolia() (gas: 3130704) +UnwindTest:test_unwind_is_authorized() (gas: 3171859) +UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 256, μ: 3862015, ~: 3862015) +UnwrapTest:test_unwrap_base(uint32) (runs: 256, μ: 3753951, ~: 3753951) +WithdrawTest:test_withdraw_arbitrum() (gas: 3951963) +WithdrawTest:test_withdraw_base() (gas: 3775357) +WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 3171611) +WithdrawTest:test_withdraw_is_authorized_base() (gas: 3171621) +WrapTest:test_wrap_arbitrum(uint32) (runs: 256, μ: 3690933, ~: 3690933) +WrapTest:test_wrap_base(uint32) (runs: 256, μ: 3600514, ~: 3600514) +ZapInTest:test_zap_in_arbitrum(uint32) (runs: 256, μ: 3823038, ~: 3823038) +ZapInTest:test_zap_in_base(uint32) (runs: 256, μ: 3720000, ~: 3720000) +ZapOutTest:test_zap_out_arbitum(uint32) (runs: 256, μ: 3781549, ~: 3781549) +ZapOutTest:test_zap_out_base(uint32) (runs: 256, μ: 3683431, ~: 3683431) \ No newline at end of file diff --git a/lcov.info b/lcov.info index 94768c3..a74eab1 100644 --- a/lcov.info +++ b/lcov.info @@ -7,7 +7,7 @@ DA:15,0 DA:17,0 FN:20,Deploy.deploySystem FNDA:0,Deploy.deploySystem -DA:34,3908 +DA:34,4107 FN:55,DeployBase.run FNDA:0,DeployBase.run DA:56,0 @@ -26,224 +26,233 @@ BRH:0 end_of_record TN: SF:src/Zap.sol -FN:46,Zap. -FNDA:3908,Zap. -DA:58,3908 -DA:61,3908 -DA:62,3908 -DA:63,3908 -DA:64,3908 -DA:65,3908 -DA:66,3908 -DA:67,3908 -DA:68,3908 -DA:71,3908 -DA:72,3908 -DA:75,3908 -DA:76,3908 -DA:77,3908 -FN:86,Zap.isAuthorized +FN:45,Zap. +FNDA:4107,Zap. +DA:57,4107 +DA:60,4107 +DA:61,4107 +DA:62,4107 +DA:63,4107 +DA:64,4107 +DA:67,4107 +DA:70,4107 +DA:71,4107 +FN:80,Zap.isAuthorized FNDA:4,Zap.isAuthorized -DA:87,4 -DA:90,4 -BRDA:90,0,0,1 -BRDA:90,0,1,- -FN:95,Zap.onlyAave -FNDA:269,Zap.onlyAave -DA:96,269 -BRDA:96,1,0,269 -BRDA:96,1,1,- -FN:110,Zap.zapIn -FNDA:516,Zap.zapIn -DA:118,516 -DA:119,516 -DA:120,516 -FN:125,Zap._zapIn -FNDA:516,Zap._zapIn -DA:132,516 -DA:133,516 -FN:142,Zap.zapOut -FNDA:516,Zap.zapOut -DA:150,516 -DA:151,516 -DA:152,516 -FN:157,Zap._zapOut -FNDA:516,Zap._zapOut -DA:164,516 -DA:165,516 -FN:181,Zap.wrap -FNDA:1032,Zap.wrap -DA:191,1032 -DA:192,1032 -DA:193,1032 -FN:198,Zap._wrap -FNDA:1548,Zap._wrap -DA:207,1548 -DA:208,1548 -DA:212,1548 -DA:213,1548 -DA:214,0 -DA:215,0 -FN:228,Zap.unwrap -FNDA:516,Zap.unwrap -DA:238,516 -DA:239,516 -DA:240,516 -DA:241,516 -FN:246,Zap._unwrap -FNDA:1032,Zap._unwrap -DA:254,1032 -DA:255,1032 -DA:256,1032 -DA:260,1032 -DA:261,1032 -DA:262,0 -DA:263,0 -FN:278,Zap.buy -FNDA:1032,Zap.buy -DA:287,1032 -DA:288,1032 -DA:289,1032 -FN:294,Zap._buy -FNDA:1548,Zap._buy -DA:302,1548 -DA:303,1548 -DA:308,1548 -DA:309,1548 -DA:310,1548 -DA:311,0 -DA:312,0 -FN:323,Zap.sell -FNDA:516,Zap.sell -DA:332,516 -DA:333,516 -DA:334,516 -DA:335,516 -FN:340,Zap._sell -FNDA:1032,Zap._sell -DA:348,1032 -DA:349,1032 -DA:350,1032 -DA:355,1032 -DA:356,1032 -DA:357,0 -DA:358,0 -FN:377,Zap.unwind +DA:81,4 +DA:84,4 +BRDA:84,0,0,1 +BRDA:84,0,1,- +FN:89,Zap.onlyAave +FNDA:256,Zap.onlyAave +DA:90,256 +BRDA:90,1,0,256 +BRDA:90,1,1,- +FN:104,Zap.zapIn +FNDA:512,Zap.zapIn +DA:112,512 +DA:113,512 +DA:114,512 +FN:119,Zap._zapIn +FNDA:512,Zap._zapIn +DA:126,512 +DA:127,512 +FN:136,Zap.zapOut +FNDA:512,Zap.zapOut +DA:144,512 +DA:145,512 +DA:146,512 +FN:151,Zap._zapOut +FNDA:512,Zap._zapOut +DA:158,512 +DA:159,512 +FN:175,Zap.wrap +FNDA:1024,Zap.wrap +DA:185,1024 +DA:186,1024 +DA:187,1024 +FN:192,Zap._wrap +FNDA:1536,Zap._wrap +DA:201,1536 +DA:202,1536 +FN:218,Zap.unwrap +FNDA:512,Zap.unwrap +DA:228,512 +DA:229,512 +DA:230,512 +DA:231,512 +FN:236,Zap._unwrap +FNDA:1024,Zap._unwrap +DA:244,1024 +DA:245,1024 +DA:246,1024 +FN:264,Zap.buy +FNDA:1024,Zap.buy +DA:273,1024 +DA:274,1024 +DA:275,1024 +DA:276,1024 +FN:281,Zap._buy +FNDA:1536,Zap._buy +DA:289,1536 +DA:290,1536 +DA:295,1536 +DA:296,1536 +DA:297,0 +DA:298,0 +FN:309,Zap.sell +FNDA:512,Zap.sell +DA:318,512 +DA:319,512 +DA:320,512 +DA:321,512 +FN:326,Zap._sell +FNDA:1024,Zap._sell +DA:334,1024 +DA:335,1024 +DA:336,1024 +FN:359,Zap.unwind FNDA:1,Zap.unwind -DA:391,0 -DA:393,0 -DA:405,0 -DA:407,0 -DA:415,0 -FN:425,Zap.executeOperation -FNDA:269,Zap.executeOperation +DA:373,0 +DA:375,0 +DA:387,0 +DA:389,0 +DA:397,0 +FN:407,Zap.executeOperation +FNDA:256,Zap.executeOperation +DA:419,0 +DA:421,0 +DA:435,0 DA:437,0 DA:439,0 -DA:453,0 -DA:455,0 -DA:457,0 -FN:465,Zap._unwind +FN:447,Zap._unwind FNDA:0,Zap._unwind -DA:474,0 -DA:483,0 -DA:498,0 -DA:502,0 -DA:506,0 -DA:510,0 -DA:514,0 -DA:521,0 -FN:529,Zap._approximateLoanNeeded +DA:456,0 +DA:465,0 +DA:481,0 +DA:485,0 +DA:493,0 +DA:497,0 +DA:501,0 +DA:508,0 +FN:516,Zap._approximateLoanNeeded FNDA:0,Zap._approximateLoanNeeded +DA:522,0 +DA:524,0 +DA:525,0 +DA:530,0 DA:535,0 -DA:537,0 -DA:538,0 -DA:543,0 -DA:548,0 -FN:561,Zap.burn +FN:548,Zap.burn FNDA:0,Zap.burn -DA:562,0 -DA:563,0 -DA:564,0 -DA:565,0 -BRDA:565,2,0,- -FN:570,Zap._burn +DA:549,0 +DA:550,0 +DA:551,0 +DA:552,0 +BRDA:552,2,0,- +FN:557,Zap._burn FNDA:0,Zap._burn -DA:571,0 -DA:572,0 -FN:585,Zap.withdraw +DA:558,0 +DA:559,0 +DA:560,0 +FN:573,Zap.withdraw FNDA:4,Zap.withdraw -DA:594,2 -DA:595,2 -DA:598,2 -FN:604,Zap._withdraw +DA:582,2 +DA:583,2 +DA:586,2 +FN:592,Zap._withdraw FNDA:2,Zap._withdraw -DA:611,2 -DA:612,2 -DA:617,2 -FN:636,Zap.quoteSwapFor +DA:599,2 +DA:600,2 +DA:605,2 +FN:624,Zap.quoteSwapFor FNDA:0,Zap.quoteSwapFor -DA:651,0 -FN:670,Zap.quoteSwapWith +DA:639,0 +FN:658,Zap.quoteSwapWith FNDA:0,Zap.quoteSwapWith -DA:685,0 -FN:700,Zap.swapFor -FNDA:264,Zap.swapFor -DA:709,264 -DA:710,264 -DA:711,1 -DA:713,1 -BRDA:713,3,0,1 -DA:714,1 -FN:720,Zap._swapFor -FNDA:264,Zap._swapFor -DA:728,264 -DA:730,264 -DA:741,264 -DA:742,1 -DA:743,1 -DA:744,263 -DA:745,263 -FN:757,Zap.swapWith +DA:673,0 +FN:688,Zap.swapFor +FNDA:257,Zap.swapFor +DA:697,257 +DA:698,257 +DA:699,1 +DA:701,1 +BRDA:701,3,0,1 +DA:702,1 +FN:708,Zap._swapFor +FNDA:257,Zap._swapFor +DA:716,257 +DA:718,257 +DA:729,257 +DA:730,1 +DA:731,1 +DA:732,256 +DA:733,256 +DA:736,1 +FN:747,Zap.swapWith FNDA:0,Zap.swapWith -DA:766,0 -DA:767,0 -DA:768,0 -FN:773,Zap._swapWith +DA:756,0 +DA:757,0 +DA:758,0 +FN:763,Zap._swapWith FNDA:0,Zap._swapWith -DA:781,0 -DA:783,0 -DA:794,0 -DA:795,0 -DA:796,0 -DA:797,0 -DA:798,0 -FN:811,Zap._pull -FNDA:4392,Zap._pull -DA:819,4392 -DA:821,4392 -DA:823,4392 -DA:824,4392 -DA:825,4392 -BRDA:825,4,0,- -BRDA:825,4,1,4392 -DA:829,0 -DA:830,0 -FN:839,Zap._push -FNDA:4132,Zap._push -DA:847,4132 -DA:849,4132 -DA:850,4132 -DA:851,4132 -BRDA:851,5,0,- -BRDA:851,5,1,4132 -DA:855,0 -DA:856,0 +DA:771,0 +DA:773,0 +DA:784,0 +DA:785,0 +DA:786,0 +DA:787,0 +DA:788,0 +FN:801,Zap._pull +FNDA:4353,Zap._pull +DA:809,4353 +DA:811,4353 +DA:813,4353 +DA:814,4353 +DA:815,0 +DA:816,0 +FN:825,Zap._push +FNDA:4100,Zap._push +DA:833,4100 +BRDA:833,4,0,- +BRDA:833,4,1,4100 +DA:834,4100 +DA:836,4100 +DA:837,4100 +DA:838,0 +DA:839,0 FNF:31 FNH:23 -LF:140 -LH:87 -BRF:10 -BRH:5 +LF:124 +LH:76 +BRF:8 +BRH:4 +end_of_record +TN: +SF:src/utils/Flush.sol +FN:16,Flush. +FNDA:0,Flush. +DA:17,0 +FN:23,Flush.flush +FNDA:0,Flush.flush +DA:24,0 +BRDA:24,0,0,- +BRDA:24,0,1,- +DA:25,0 +DA:26,0 +DA:27,0 +BRDA:27,1,0,- +FN:34,Flush.designatePlumber +FNDA:0,Flush.designatePlumber +DA:35,0 +BRDA:35,2,0,- +BRDA:35,2,1,- +DA:36,0 +FNF:3 +FNH:0 +LF:7 +LH:0 +BRF:5 +BRH:0 end_of_record TN: SF:src/utils/Reentrancy.sol @@ -251,10 +260,10 @@ FN:27,Reentrancy.requireStage FNDA:0,Reentrancy.requireStage DA:28,0 FN:32,Reentrancy._requireStage -FNDA:429,Reentrancy._requireStage -DA:33,429 -BRDA:33,0,0,168 -BRDA:33,0,1,261 +FNDA:423,Reentrancy._requireStage +DA:33,423 +BRDA:33,0,0,167 +BRDA:33,0,1,256 FNF:2 FNH:1 LF:2 @@ -265,50 +274,50 @@ end_of_record TN: SF:test/utils/Bootstrap.sol FN:40,Bootstrap.setUp -FNDA:32,Bootstrap.setUp -DA:41,32 -DA:42,32 -DA:43,32 -DA:44,32 -DA:46,32 -DA:47,32 -DA:48,32 +FNDA:33,Bootstrap.setUp +DA:41,33 +DA:42,33 +DA:43,33 +DA:44,33 +DA:46,33 +DA:47,33 +DA:48,33 FN:52,Bootstrap.base -FNDA:1551,Bootstrap.base -DA:54,1551 -DA:57,1551 -DA:70,1551 -DA:71,1551 -DA:72,1551 -DA:73,1551 -DA:74,1551 -DA:75,1551 +FNDA:1539,Bootstrap.base +DA:54,1539 +DA:57,1539 +DA:70,1539 +DA:71,1539 +DA:72,1539 +DA:73,1539 +DA:74,1539 +DA:75,1539 FN:80,Bootstrap.arbitrum -FNDA:2083,Bootstrap.arbitrum -DA:82,2083 -DA:85,2083 -DA:98,2083 -DA:99,2083 -DA:100,2083 -DA:101,2083 -DA:102,2083 -DA:103,2083 +FNDA:2051,Bootstrap.arbitrum +DA:82,2051 +DA:85,2051 +DA:98,2051 +DA:99,2051 +DA:100,2051 +DA:101,2051 +DA:102,2051 +DA:103,2051 FN:108,Bootstrap.arbitrum_sepolia -FNDA:272,Bootstrap.arbitrum_sepolia -DA:110,272 -DA:113,272 -DA:126,272 -DA:127,272 -DA:128,272 -DA:129,272 -DA:130,272 -DA:131,272 +FNDA:515,Bootstrap.arbitrum_sepolia +DA:110,515 +DA:113,515 +DA:126,515 +DA:127,515 +DA:128,515 +DA:129,515 +DA:130,515 +DA:131,515 FN:142,Bootstrap._spin -FNDA:3362,Bootstrap._spin -DA:150,3362 -DA:151,3362 -DA:152,3362 -DA:153,3362 +FNDA:3331,Bootstrap._spin +DA:150,3331 +DA:151,3331 +DA:152,3331 +DA:153,3331 FNF:5 FNH:5 LF:35 From 924063de3479a1e810bdeaac773541f7197b244c Mon Sep 17 00:00:00 2001 From: Florian <123545417+Flocqst@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:01:29 +0200 Subject: [PATCH 104/129] =?UTF-8?q?=F0=9F=91=B7=20burn=20returns=20remaini?= =?UTF-8?q?ng=20value=20(#47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index c2f0c71..aeb58bb 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -545,10 +545,17 @@ contract Zap is Reentrancy, Errors { /// @dev excess USDx will be returned to the caller /// @param _amount amount of USDx to burn /// @param _accountId synthetix perp market account id - function burn(uint256 _amount, uint128 _accountId) external { + /// @return remaining amount of USDx returned to the caller + function burn( + uint256 _amount, + uint128 _accountId + ) + external + returns (uint256 remaining) + { _pull(USDX, msg.sender, _amount); _burn(_amount, _accountId); - uint256 remaining = IERC20(USDX).balanceOf(address(this)); + remaining = IERC20(USDX).balanceOf(address(this)); if (remaining > 0) _push(USDX, msg.sender, remaining); } From 7f25fa5700d9ef074518a898db98f3754f951bc9 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 15 Oct 2024 10:25:05 -0700 Subject: [PATCH 105/129] =?UTF-8?q?=F0=9F=91=B7=F0=9F=8F=BB=E2=80=8D?= =?UTF-8?q?=E2=99=82=EF=B8=8F=20Final=20Audit=20Fix=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🪲 SafeTransferFrom * ⛳️ remove try catch and fmt * 👷 added PlumberDesignated event * ✨ removed unused safe erc20 methods * ⛳ revert if an amount of zero is passed to _push and _pull --------- Co-authored-by: meb <4982406+barrasso@users.noreply.github.com> --- src/Zap.sol | 40 ++++----------- src/utils/Flush.sol | 4 ++ src/utils/SafeTransferERC20.sol | 90 +++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 31 deletions(-) create mode 100644 src/utils/SafeTransferERC20.sol diff --git a/src/Zap.sol b/src/Zap.sol index aeb58bb..3f80588 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -7,6 +7,7 @@ import {IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; import {IQuoter, IRouter} from "./interfaces/IUniswap.sol"; import {Errors} from "./utils/Errors.sol"; import {Reentrancy} from "./utils/Reentrancy.sol"; +import {SafeERC20} from "./utils/SafeTransferERC20.sol"; /// @title zap /// @custom:synthetix zap USDC into and out of USDx @@ -287,16 +288,12 @@ contract Zap is Reentrancy, Errors { returns (uint256 received) { IERC20(USDX).approve(SPOT_MARKET, _amount); - try ISpotMarket(SPOT_MARKET).buy({ + (received,) = ISpotMarket(SPOT_MARKET).buy({ marketId: _synthId, usdAmount: _amount, minAmountReceived: _tolerance, referrer: REFERRER - }) returns (uint256 amount, ISpotMarket.Data memory) { - received = amount; - } catch Error(string memory reason) { - revert BuyFailed(reason); - } + }); } /// @notice sell synth via synthetix spot market @@ -461,8 +458,7 @@ contract Zap is Reentrancy, Errors { uint256 _zapTolerance, uint256 _unwrapTolerance, uint256 _swapTolerance, - /* address _receiver */ - ) = abi.decode( + ) = /* address _receiver */ abi.decode( _params, ( uint128, @@ -804,47 +800,29 @@ contract Zap is Reentrancy, Errors { /// @param _token address of token to pull /// @param _from address of sender /// @param _amount amount of token to pull - /// @return success boolean representing execution success - function _pull( - address _token, - address _from, - uint256 _amount - ) - internal - returns (bool) - { + function _pull(address _token, address _from, uint256 _amount) internal { + require(_amount > 0, PullFailed("Zero Amount")); IERC20 token = IERC20(_token); - try token.transferFrom(_from, address(this), _amount) returns ( - bool result - ) { - return result; - } catch Error(string memory reason) { - revert PullFailed(bytes(reason)); - } + SafeERC20.safeTransferFrom(token, _from, address(this), _amount); } /// @dev push tokens to a receiver /// @param _token address of token to push /// @param _receiver address of receiver /// @param _amount amount of token to push - /// @return success boolean representing execution success function _push( address _token, address _receiver, uint256 _amount ) internal - returns (bool) { require(_receiver != address(0), PushFailed("Zero Address")); + require(_amount > 0, PushFailed("Zero Amount")); IERC20 token = IERC20(_token); - try token.transfer(_receiver, _amount) returns (bool result) { - return result; - } catch Error(string memory reason) { - revert PushFailed(bytes(reason)); - } + SafeERC20.safeTransfer(token, _receiver, _amount); } } diff --git a/src/utils/Flush.sol b/src/utils/Flush.sol index 46267fb..fa40c28 100644 --- a/src/utils/Flush.sol +++ b/src/utils/Flush.sol @@ -13,6 +13,9 @@ contract Flush { /// @notice thrown when caller is not the plumber error OnlyPlumber(); + ///@notice emitted when a new plumber is designated + event PlumberDesignated(address plumber); + constructor(address _plumber) { PLUMBER = _plumber; } @@ -34,6 +37,7 @@ contract Flush { function designatePlumber(address _newPlumber) external { require(msg.sender == PLUMBER, OnlyPlumber()); PLUMBER = _newPlumber; + emit PlumberDesignated(_newPlumber); } } diff --git a/src/utils/SafeTransferERC20.sol b/src/utils/SafeTransferERC20.sol new file mode 100644 index 0000000..102d509 --- /dev/null +++ b/src/utils/SafeTransferERC20.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) +// (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../interfaces/IERC20.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC-20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to + * your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, + * etc. + */ +library SafeERC20 { + + /** + * @dev An operation with an ERC-20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Transfer `value` amount of `token` from the calling contract to + * `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the + * approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are + * assumed to be successful. + */ + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) + internal + { + _callOptionalReturn( + token, abi.encodeCall(token.transferFrom, (from, to, value)) + ); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to + * a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is + * returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its + * variants). + * + * This is a variant of {_callOptionalReturnBool} that reverts if call fails + * to meet the requirements. + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + uint256 returnSize; + uint256 returnValue; + assembly ("memory-safe") { + let success := + call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) + // bubble errors + if iszero(success) { + let ptr := mload(0x40) + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + returnSize := returndatasize() + returnValue := mload(0) + } + + if ( + returnSize == 0 ? address(token).code.length == 0 : returnValue != 1 + ) { + revert SafeERC20FailedOperation(address(token)); + } + } + +} From 09f4ed1544e25017e76825b419a657f249da46ce Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 15 Oct 2024 14:41:36 -0700 Subject: [PATCH 106/129] =?UTF-8?q?=F0=9F=91=B7=F0=9F=8F=BB=E2=80=8D?= =?UTF-8?q?=E2=99=82=EF=B8=8F=20Multihop=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 👷 SingleSwaps -> MultiHop WIP no tests * ✅ swapFor tests * ✨ added comment to prevent path gotcha * fixed quote fns * swap tests * fmt * 👷 added return vars back to quote fns * ✨ wasp review fixes * 👷 fixed IUniswap Quoter interface * 👷 fixed execute operation bytes decoding --- src/Zap.sol | 188 ++++++++++++++++++------------------ src/interfaces/IUniswap.sol | 91 +++++++++-------- test/Swap.for.t.sol | 71 +++++++++++++- test/Unwind.t.sol | 2 +- test/utils/Bootstrap.sol | 4 +- test/utils/Constants.sol | 5 + 6 files changed, 224 insertions(+), 137 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 3f80588..20c2f28 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -349,6 +349,7 @@ contract Zap is Reentrancy, Errors { /// @param _collateralId synthetix market id of collateral /// @param _collateralAmount amount of collateral to unwind /// @param _collateral address of collateral to unwind + /// @param _path Uniswap swap path encoded in reverse order /// @param _zapTolerance acceptable slippage for zapping /// @param _unwrapTolerance acceptable slippage for unwrapping /// @param _swapTolerance acceptable slippage for swapping @@ -358,6 +359,7 @@ contract Zap is Reentrancy, Errors { uint128 _collateralId, uint256 _collateralAmount, address _collateral, + bytes memory _path, uint256 _zapTolerance, uint256 _unwrapTolerance, uint256 _swapTolerance, @@ -374,6 +376,7 @@ contract Zap is Reentrancy, Errors { _collateralId, _collateralAmount, _collateral, + _path, _zapTolerance, _unwrapTolerance, _swapTolerance, @@ -415,13 +418,14 @@ contract Zap is Reentrancy, Errors { { stage = Stage.LEVEL2; - (,,, address _collateral,,,, address _receiver) = abi.decode( + (,,, address _collateral,,,,, address _receiver) = abi.decode( _params, ( uint128, uint128, uint256, address, + bytes, uint256, uint256, uint256, @@ -450,21 +454,65 @@ contract Zap is Reentrancy, Errors { requireStage(Stage.LEVEL2) returns (uint256 unwound) { - ( - uint128 _accountId, - uint128 _collateralId, - uint256 _collateralAmount, - address _collateral, - uint256 _zapTolerance, - uint256 _unwrapTolerance, - uint256 _swapTolerance, - ) = /* address _receiver */ abi.decode( + { + ( + uint128 _accountId, + uint128 _collateralId, + uint256 _collateralAmount, + , + , + uint256 _zapTolerance, + uint256 _unwrapTolerance, + , + ) = abi.decode( + _params, + ( + uint128, + uint128, + uint256, + address, + bytes, + uint256, + uint256, + uint256, + address + ) + ); + + // zap USDC from flashloan into USDx; + // ALL USDC flashloaned from Aave is zapped into USDx + uint256 usdxAmount = _zapIn(_flashloan, _zapTolerance); + + // burn USDx to pay off synthetix perp position debt; + // debt is denominated in USD and thus repaid with USDx + _burn(usdxAmount, _accountId); + + /// @dev given the USDC buffer, an amount of USDx + /// necessarily less than the buffer will remain (<$1); + /// this amount is captured by the protocol + // withdraw synthetix perp position collateral to this contract; + // i.e., # of sETH, # of sUSDe, # of sUSDC (...) + _withdraw(_collateralId, _collateralAmount, _accountId); + + // unwrap withdrawn synthetix perp position collateral; + // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) + unwound = + _unwrap(_collateralId, _collateralAmount, _unwrapTolerance); + + // establish total debt now owed to Aave; + // i.e., # of USDC + _flashloan += _premium; + } + + (,,, address _collateral, bytes memory _path,,, uint256 _swapTolerance,) + = abi.decode( _params, ( uint128, uint128, uint256, address, + bytes, uint256, uint256, uint256, @@ -472,30 +520,6 @@ contract Zap is Reentrancy, Errors { ) ); - // zap USDC from flashloan into USDx; - // ALL USDC flashloaned from Aave is zapped into USDx - uint256 usdxAmount = _zapIn(_flashloan, _zapTolerance); - - // burn USDx to pay off synthetix perp position debt; - // debt is denominated in USD and thus repaid with USDx - _burn(usdxAmount, _accountId); - - /// @dev given the USDC buffer, an amount of USDx - /// necessarily less than the buffer will remain (<$1); - /// this amount is captured by the protocol - - // withdraw synthetix perp position collateral to this contract; - // i.e., # of sETH, # of sUSDe, # of sUSDC (...) - _withdraw(_collateralId, _collateralAmount, _accountId); - - // unwrap withdrawn synthetix perp position collateral; - // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) - unwound = _unwrap(_collateralId, _collateralAmount, _unwrapTolerance); - - // establish total debt now owed to Aave; - // i.e., # of USDC - _flashloan += _premium; - // swap as much (or little) as necessary to repay Aave flashloan; // i.e., WETH -(swap)-> USDC -(repay)-> Aave // i.e., USDe -(swap)-> USDC -(repay)-> Aave @@ -503,7 +527,7 @@ contract Zap is Reentrancy, Errors { // whatever collateral amount is remaining is returned to the caller unwound -= _collateral == USDC ? _flashloan - : _swapFor(_collateral, _flashloan, _swapTolerance); + : _swapFor(_collateral, _path, _flashloan, _swapTolerance); } /// @notice approximate USDC needed to unwind synthetix perp position @@ -613,83 +637,64 @@ contract Zap is Reentrancy, Errors { //////////////////////////////////////////////////////////////*/ /// @notice query amount required to receive a specific amount of token + /// @dev this is the QuoterV1 interface + /// @dev _path MUST be encoded backwards for `exactOutput` /// @dev quoting is NOT gas efficient and should NOT be called on chain /// @custom:integrator quoting function inclusion is for QoL purposes - /// @param _tokenIn address of token being swapped in - /// @param _tokenOut address of token being swapped out + /// @param _path Uniswap swap path encoded in reverse order /// @param _amountOut is the desired output amount - /// @param _fee of the token pool to consider for the pair - /// @param _sqrtPriceLimitX96 of the pool; cannot be exceeded for swap /// @return amountIn required as the input for the swap in order - /// @return sqrtPriceX96After of the pool after the swap - /// @return initializedTicksCrossed during the quoted swap - /// @return gasEstimate of gas that the swap will consume function quoteSwapFor( - address _tokenIn, - address _tokenOut, - uint256 _amountOut, - uint24 _fee, - uint160 _sqrtPriceLimitX96 + bytes memory _path, + uint256 _amountOut ) external returns ( uint256 amountIn, - uint160 sqrtPriceX96After, - uint32 initializedTicksCrossed, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, uint256 gasEstimate ) { - return IQuoter(QUOTER).quoteExactOutputSingle( - IQuoter.QuoteExactOutputSingleParams( - _tokenIn, _tokenOut, _amountOut, _fee, _sqrtPriceLimitX96 - ) - ); + return IQuoter(QUOTER).quoteExactOutput(_path, _amountOut); } /// @notice query amount received for a specific amount of token to spend + /// @dev this is the QuoterV1 interface + /// @dev _path MUST be encoded in order for `exactInput` /// @dev quoting is NOT gas efficient and should NOT be called on chain /// @custom:integrator quoting function inclusion is for QoL purposes - /// @param _tokenIn address of token being swapped in - /// @param _tokenOut address of token being swapped out - /// @param _amountIn is the input amount to spend - /// @param _fee of the token pool to consider for the pair - /// @param _sqrtPriceLimitX96 of the pool; cannot be exceeded for swap + /// @param _path Uniswap swap path encoded in order + /// @param _amountIn is the input amount to spendp /// @return amountOut received as the output for the swap in order - /// @return sqrtPriceX96After of the pool after the swap - /// @return initializedTicksCrossed during the quoted swap - /// @return gasEstimate of gas that the swap will consume function quoteSwapWith( - address _tokenIn, - address _tokenOut, - uint256 _amountIn, - uint24 _fee, - uint160 _sqrtPriceLimitX96 + bytes memory _path, + uint256 _amountIn ) external returns ( uint256 amountOut, - uint160 sqrtPriceX96After, - uint32 initializedTicksCrossed, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, uint256 gasEstimate ) { - return IQuoter(QUOTER).quoteExactInputSingle( - IQuoter.QuoteExactInputSingleParams( - _tokenIn, _tokenOut, _amountIn, _fee, _sqrtPriceLimitX96 - ) - ); + return IQuoter(QUOTER).quoteExactInput(_path, _amountIn); } /// @notice swap a tolerable amount of tokens for a specific amount of USDC + /// @dev _path MUST be encoded backwards for `exactOutput` /// @dev caller must grant token allowance to this contract /// @dev any excess token not spent will be returned to the caller /// @param _from address of token to swap + /// @param _path uniswap swap path encoded in reverse order /// @param _amount amount of USDC to receive in return /// @param _tolerance or tolerable amount of token to spend /// @param _receiver address to receive USDC /// @return deducted amount of incoming token; i.e., amount spent function swapFor( address _from, + bytes memory _path, uint256 _amount, uint256 _tolerance, address _receiver @@ -698,7 +703,7 @@ contract Zap is Reentrancy, Errors { returns (uint256 deducted) { _pull(_from, msg.sender, _tolerance); - deducted = _swapFor(_from, _amount, _tolerance); + deducted = _swapFor(_from, _path, _amount, _tolerance); _push(USDC, _receiver, _amount); if (deducted < _tolerance) { @@ -710,6 +715,7 @@ contract Zap is Reentrancy, Errors { /// @dev following execution, this contract will hold the swapped USDC function _swapFor( address _from, + bytes memory _path, uint256 _amount, uint256 _tolerance ) @@ -718,19 +724,14 @@ contract Zap is Reentrancy, Errors { { IERC20(_from).approve(ROUTER, _tolerance); - IRouter.ExactOutputSingleParams memory params = IRouter - .ExactOutputSingleParams({ - tokenIn: _from, - tokenOut: USDC, - fee: FEE_TIER, + IRouter.ExactOutputParams memory params = IRouter.ExactOutputParams({ + path: _path, recipient: address(this), amountOut: _amount, - amountInMaximum: _tolerance, - sqrtPriceLimitX96: 0 + amountInMaximum: _tolerance }); - try IRouter(ROUTER).exactOutputSingle(params) returns (uint256 amountIn) - { + try IRouter(ROUTER).exactOutput(params) returns (uint256 amountIn) { deducted = amountIn; } catch Error(string memory reason) { revert SwapFailed(reason); @@ -740,8 +741,10 @@ contract Zap is Reentrancy, Errors { } /// @notice swap a specific amount of tokens for a tolerable amount of USDC + /// @dev _path MUST be encoded in order for `exactInput` /// @dev caller must grant token allowance to this contract /// @param _from address of token to swap + /// @param _path uniswap swap path encoded in order /// @param _amount of token to swap /// @param _tolerance tolerable amount of USDC to receive specified with 6 /// decimals @@ -749,6 +752,7 @@ contract Zap is Reentrancy, Errors { /// @return received amount of USDC function swapWith( address _from, + bytes memory _path, uint256 _amount, uint256 _tolerance, address _receiver @@ -757,7 +761,7 @@ contract Zap is Reentrancy, Errors { returns (uint256 received) { _pull(_from, msg.sender, _amount); - received = _swapWith(_from, _amount, _tolerance); + received = _swapWith(_from, _path, _amount, _tolerance); _push(USDC, _receiver, received); } @@ -765,6 +769,7 @@ contract Zap is Reentrancy, Errors { /// @dev following execution, this contract will hold the swapped USDC function _swapWith( address _from, + bytes memory _path, uint256 _amount, uint256 _tolerance ) @@ -773,19 +778,14 @@ contract Zap is Reentrancy, Errors { { IERC20(_from).approve(ROUTER, _amount); - IRouter.ExactInputSingleParams memory params = IRouter - .ExactInputSingleParams({ - tokenIn: _from, - tokenOut: USDC, - fee: FEE_TIER, + IRouter.ExactInputParams memory params = IRouter.ExactInputParams({ + path: _path, recipient: address(this), amountIn: _amount, - amountOutMinimum: _tolerance, - sqrtPriceLimitX96: 0 + amountOutMinimum: _tolerance }); - try IRouter(ROUTER).exactInputSingle(params) returns (uint256 amountOut) - { + try IRouter(ROUTER).exactInput(params) returns (uint256 amountOut) { received = amountOut; } catch Error(string memory reason) { revert SwapFailed(reason); diff --git a/src/interfaces/IUniswap.sol b/src/interfaces/IUniswap.sol index c4109ec..ac73554 100644 --- a/src/interfaces/IUniswap.sol +++ b/src/interfaces/IUniswap.sol @@ -3,32 +3,36 @@ pragma solidity 0.8.27; interface IRouter { - struct ExactInputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; + struct ExactInputParams { + bytes path; address recipient; uint256 amountIn; uint256 amountOutMinimum; - uint160 sqrtPriceLimitX96; } - function exactInputSingle(ExactInputSingleParams calldata params) - external - payable - returns (uint256 amountOut); - - struct ExactOutputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; + struct ExactOutputParams { + bytes path; address recipient; uint256 amountOut; uint256 amountInMaximum; - uint160 sqrtPriceLimitX96; } - function exactOutputSingle(ExactOutputSingleParams calldata params) + /// @notice Swaps `amountIn` of one token for as much as possible of another + /// along the specified path + /// @param params The parameters necessary for the multi-hop swap, encoded + /// as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) + external + payable + returns (uint256 amountOut); + + /// @notice Swaps as little as possible of one token for `amountOut` of + /// another along the specified path (reversed) + /// @param params The parameters necessary for the multi-hop swap, encoded + /// as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); @@ -37,37 +41,48 @@ interface IRouter { interface IQuoter { - struct QuoteExactInputSingleParams { - address tokenIn; - address tokenOut; - uint256 amountIn; - uint24 fee; - uint160 sqrtPriceLimitX96; - } - - function quoteExactInputSingle(QuoteExactInputSingleParams memory params) + /// @notice Returns the amount out received for a given exact input swap + /// without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee + /// @param amountIn The amount of the first token to swap + /// @return amountOut The amount of the last token that would be received + /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for + /// each pool in the path + /// @return initializedTicksCrossedList List of the initialized ticks that + /// the swap crossed for each pool in the path + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactInput( + bytes memory path, + uint256 amountIn + ) external returns ( uint256 amountOut, - uint160 sqrtPriceX96After, - uint32 initializedTicksCrossed, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, uint256 gasEstimate ); - struct QuoteExactOutputSingleParams { - address tokenIn; - address tokenOut; - uint256 amount; - uint24 fee; - uint160 sqrtPriceLimitX96; - } - - function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) + /// @notice Returns the amount in required for a given exact output swap + /// without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee. + /// Path must be provided in reverse order + /// @param amountOut The amount of the last token to receive + /// @return amountIn The amount of first token required to be paid + /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for + /// each pool in the path + /// @return initializedTicksCrossedList List of the initialized ticks that + /// the swap crossed for each pool in the path + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactOutput( + bytes memory path, + uint256 amountOut + ) external returns ( uint256 amountIn, - uint160 sqrtPriceX96After, - uint32 initializedTicksCrossed, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, uint256 gasEstimate ); diff --git a/test/Swap.for.t.sol b/test/Swap.for.t.sol index a6ee7c4..73d63ed 100644 --- a/test/Swap.for.t.sol +++ b/test/Swap.for.t.sol @@ -17,7 +17,7 @@ import { contract SwapForTest is Bootstrap { - function test_swap_for_base() public base { + function test_swap_for_single_base() public base { uint256 amount = 100e6; uint256 tolerance = type(uint256).max / 4; _spin(ACTOR, weth, tolerance, address(zap)); @@ -26,6 +26,7 @@ contract SwapForTest is Bootstrap { vm.startPrank(ACTOR); zap.swapFor({ _from: address(weth), + _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), _amount: amount, _tolerance: tolerance, _receiver: ACTOR @@ -36,7 +37,7 @@ contract SwapForTest is Bootstrap { vm.stopPrank(); } - function test_swap_for_arbitrum(uint8 percentage) public arbitrum { + function test_swap_for_single_arbitrum(uint8 percentage) public arbitrum { vm.assume(percentage < 95 && percentage > 0); uint256 tolerance = type(uint256).max; @@ -59,6 +60,7 @@ contract SwapForTest is Bootstrap { zap.swapFor({ _from: address(weth), + _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), _amount: amount, _tolerance: tolerance, _receiver: ACTOR @@ -76,7 +78,70 @@ contract SwapForTest is Bootstrap { } /// @custom:todo - function test_swap_for_arbitrum_sepolia() public arbitrum_sepolia {} + function test_swap_for_single_arbitrum_sepolia() public arbitrum_sepolia {} + + function test_swap_for_multihop_base() public base { + uint256 amount = 100e6; + uint256 tolerance = type(uint256).max / 4; + _spin(ACTOR, weth, tolerance, address(zap)); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(weth.balanceOf(ACTOR), tolerance); + vm.startPrank(ACTOR); + zap.swapFor({ + _from: address(weth), + _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), + _amount: amount, + _tolerance: tolerance, + _receiver: ACTOR + }); + assertGt(usdc.balanceOf(ACTOR), 0); + assertLt(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); + vm.stopPrank(); + } + + function test_swap_for_multihop_arbitrum(uint8 percentage) + public + arbitrum + { + vm.assume(percentage < 95 && percentage > 0); + + uint256 tolerance = type(uint256).max; + _spin(ACTOR, weth, tolerance, address(zap)); + + address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( + address(weth), address(usdc), zap.FEE_TIER() + ); + uint256 depth = usdc.balanceOf(pool); + uint256 amount = depth * (percentage / 100); + + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(weth.balanceOf(ACTOR), tolerance); + + vm.startPrank(ACTOR); + + if (amount == 0) { + vm.expectRevert(); + } + + zap.swapFor({ + _from: address(weth), + _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), + _amount: amount, + _tolerance: tolerance, + _receiver: ACTOR + }); + + assertTrue( + amount > 1e6 + ? usdc.balanceOf(ACTOR) < 0 + : usdc.balanceOf(ACTOR) == 0 + ); + assertLe(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); + + vm.stopPrank(); + } bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; diff --git a/test/Unwind.t.sol b/test/Unwind.t.sol index e1ac19c..21f0e03 100644 --- a/test/Unwind.t.sol +++ b/test/Unwind.t.sol @@ -18,7 +18,7 @@ contract UnwindTest is Bootstrap, Errors { function test_unwind_is_authorized() public arbitrum { vm.prank(ACTOR); vm.expectRevert(NotPermitted.selector); - zap.unwind(0, 0, 0, address(0), 0, 0, 0, address(0)); + zap.unwind(0, 0, 0, address(0), "", /*todo*/ 0, 0, 0, address(0)); } /// @custom:todo diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol index 5c03c69..d71b65d 100644 --- a/test/utils/Bootstrap.sol +++ b/test/utils/Bootstrap.sol @@ -36,6 +36,7 @@ contract Bootstrap is IERC20 susdc; IERC20 usdx; IERC20 weth; + IERC20 tbtc; function setUp() public { string memory BASE_RPC = vm.envString(BASE_RPC_REF); @@ -73,6 +74,7 @@ contract Bootstrap is susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); usdx = IERC20(BASE_USDX); weth = IERC20(BASE_WETH); + tbtc = IERC20(BASE_TBTC); _; } @@ -101,7 +103,7 @@ contract Bootstrap is susdc = IERC20(spotMarket.getSynth(zap.SUSDC_SPOT_ID())); usdx = IERC20(ARBITRUM_USDX); weth = IERC20(ARBITRUM_WETH); - + tbtc = IERC20(ARBITRUM_TBTC); _; } diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index b443359..b9a050d 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -17,8 +17,13 @@ contract Constants { /// @custom:tokens address constant ARBITRUM_WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + address constant ARBITRUM_TBTC = 0x6c84a8f1c29108F47a79964b5Fe888D4f4D0dE40; address constant ARBITRUM_SEPOLIA_WETH = address(0); address constant BASE_WETH = 0x4200000000000000000000000000000000000006; + address constant BASE_TBTC = 0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b; + + uint24 constant FEE_30 = 3000; + uint24 constant FEE_100 = 10_000; /// @custom:synthetix bytes32 constant _ADMIN_PERMISSION = "ADMIN"; From 660b2f81f739a019efb049c47d1c91fd6533c688 Mon Sep 17 00:00:00 2001 From: meb <4982406+barrasso@users.noreply.github.com> Date: Wed, 16 Oct 2024 09:44:28 -0400 Subject: [PATCH 107/129] =?UTF-8?q?=E2=9C=85=20fix=20zap=20out=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Zap.out.t.sol | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/test/Zap.out.t.sol b/test/Zap.out.t.sol index 6f3c92a..1959a93 100644 --- a/test/Zap.out.t.sol +++ b/test/Zap.out.t.sol @@ -1,20 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import { - Bootstrap, - Constants, - IERC20, - IPerpsMarket, - IPool, - ISpotMarket, - Test, - Zap -} from "./utils/Bootstrap.sol"; +import {Bootstrap, Constants, IERC20, IPerpsMarket, IPool, ISpotMarket, Zap} from "./utils/Bootstrap.sol"; contract ZapOutTest is Bootstrap { - - function test_zap_out_base(uint32 amount) public base { + function test_zap_out_base(uint64 amount) public base { + vm.assume(amount > 1e18); _spin(ACTOR, usdx, amount, address(zap)); assertEq(usdc.balanceOf(ACTOR), 0); assertEq(usdx.balanceOf(ACTOR), amount); @@ -30,7 +21,8 @@ contract ZapOutTest is Bootstrap { assertEq(usdx.balanceOf(ACTOR), 0); } - function test_zap_out_arbitum(uint32 amount) public arbitrum { + function test_zap_out_arbitum(uint64 amount) public arbitrum { + vm.assume(amount > 1e18); _spin(ACTOR, usdx, amount, address(zap)); assertEq(usdc.balanceOf(ACTOR), 0); assertEq(usdx.balanceOf(ACTOR), amount); @@ -45,5 +37,4 @@ contract ZapOutTest is Bootstrap { assertEq(usdc.balanceOf(ACTOR), zapped); assertEq(usdx.balanceOf(ACTOR), 0); } - } From 35e517eceade43560c1eb54d47de1fc3aa949331 Mon Sep 17 00:00:00 2001 From: meb <4982406+barrasso@users.noreply.github.com> Date: Wed, 16 Oct 2024 09:46:09 -0400 Subject: [PATCH 108/129] =?UTF-8?q?=E2=9C=A8=20fix=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/Zap.out.t.sol | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/Zap.out.t.sol b/test/Zap.out.t.sol index 1959a93..3f1dd1c 100644 --- a/test/Zap.out.t.sol +++ b/test/Zap.out.t.sol @@ -1,9 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.27; -import {Bootstrap, Constants, IERC20, IPerpsMarket, IPool, ISpotMarket, Zap} from "./utils/Bootstrap.sol"; +import { + Bootstrap, + Constants, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Zap +} from "./utils/Bootstrap.sol"; contract ZapOutTest is Bootstrap { + function test_zap_out_base(uint64 amount) public base { vm.assume(amount > 1e18); _spin(ACTOR, usdx, amount, address(zap)); @@ -37,4 +46,5 @@ contract ZapOutTest is Bootstrap { assertEq(usdc.balanceOf(ACTOR), zapped); assertEq(usdx.balanceOf(ACTOR), 0); } + } From 504aaaaf32a0cd71b9234faf61ee0d5380818647 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 17 Oct 2024 11:56:43 -0700 Subject: [PATCH 109/129] =?UTF-8?q?=F0=9F=93=9A=20docs=20(#53)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.gitignore | 1 + docs/book.css | 13 + docs/book.toml | 12 + docs/solidity.min.js | 74 ++ docs/src/README.md | 65 ++ docs/src/SUMMARY.md | 16 + docs/src/src/README.md | 6 + docs/src/src/Zap.sol/contract.Zap.md | 904 ++++++++++++++++++ .../interfaces/IAave.sol/interface.IPool.md | 19 + .../interfaces/IERC20.sol/interface.IERC20.md | 59 ++ .../ISynthetix.sol/interface.IPerpsMarket.md | 52 + .../ISynthetix.sol/interface.ISpotMarket.md | 81 ++ .../IUniswap.sol/interface.IQuoter.md | 77 ++ .../IUniswap.sol/interface.IRouter.md | 78 ++ docs/src/src/interfaces/README.md | 9 + .../src/utils/Errors.sol/contract.Errors.md | 136 +++ .../src/src/utils/Flush.sol/contract.Flush.md | 73 ++ docs/src/src/utils/README.md | 7 + .../Reentrancy.sol/contract.Reentrancy.md | 71 ++ .../library.SafeERC20.md | 71 ++ 20 files changed, 1824 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/book.css create mode 100644 docs/book.toml create mode 100644 docs/solidity.min.js create mode 100644 docs/src/README.md create mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/src/README.md create mode 100644 docs/src/src/Zap.sol/contract.Zap.md create mode 100644 docs/src/src/interfaces/IAave.sol/interface.IPool.md create mode 100644 docs/src/src/interfaces/IERC20.sol/interface.IERC20.md create mode 100644 docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md create mode 100644 docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md create mode 100644 docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md create mode 100644 docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md create mode 100644 docs/src/src/interfaces/README.md create mode 100644 docs/src/src/utils/Errors.sol/contract.Errors.md create mode 100644 docs/src/src/utils/Flush.sol/contract.Flush.md create mode 100644 docs/src/src/utils/README.md create mode 100644 docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md create mode 100644 docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..4e42a1b --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +book/ \ No newline at end of file diff --git a/docs/book.css b/docs/book.css new file mode 100644 index 0000000..b5ce903 --- /dev/null +++ b/docs/book.css @@ -0,0 +1,13 @@ +table { + margin: 0 auto; + border-collapse: collapse; + width: 100%; +} + +table td:first-child { + width: 15%; +} + +table td:nth-child(2) { + width: 25%; +} \ No newline at end of file diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 0000000..00b424c --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,12 @@ +[book] +src = "src" +title = "" + +[output.html] +no-section-label = true +additional-js = ["solidity.min.js"] +additional-css = ["book.css"] +git-repository-url = "https://github.com/moss-eth/zap" + +[output.html.fold] +enable = true diff --git a/docs/solidity.min.js b/docs/solidity.min.js new file mode 100644 index 0000000..1924932 --- /dev/null +++ b/docs/solidity.min.js @@ -0,0 +1,74 @@ +hljs.registerLanguage("solidity",(()=>{"use strict";function e(){try{return!0 +}catch(e){return!1}} +var a=/-?(\b0[xX]([a-fA-F0-9]_?)*[a-fA-F0-9]|(\b[1-9](_?\d)*(\.((\d_?)*\d)?)?|\.\d(_?\d)*)([eE][-+]?\d(_?\d)*)?|\b0)(?!\w|\$)/ +;e()&&(a=a.source.replace(/\\b/g,"(?{ +var a=r(e),o=l(e),c=/[A-Za-z_$][A-Za-z_$0-9.]*/,d=e.inherit(e.TITLE_MODE,{ +begin:/[A-Za-z$_][0-9A-Za-z$_]*/,lexemes:c,keywords:n}),u={className:"params", +begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,lexemes:c,keywords:n, +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,o,s]},_={ +className:"operator",begin:/:=|->/};return{keywords:n,lexemes:c, +contains:[a,o,i,t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,_,{ +className:"function",lexemes:c,beginKeywords:"function",end:"{",excludeEnd:!0, +contains:[d,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,_]}]}}, +solAposStringMode:r,solQuoteStringMode:l,HEX_APOS_STRING_MODE:i, +HEX_QUOTE_STRING_MODE:t,SOL_NUMBER:s,isNegativeLookbehindAvailable:e} +;const{baseAssembly:c,solAposStringMode:d,solQuoteStringMode:u,HEX_APOS_STRING_MODE:_,HEX_QUOTE_STRING_MODE:m,SOL_NUMBER:b,isNegativeLookbehindAvailable:E}=o +;return e=>{for(var a=d(e),s=u(e),n=[],i=0;i<32;i++)n[i]=i+1 +;var t=n.map((e=>8*e)),r=[];for(i=0;i<=80;i++)r[i]=i +;var l=n.map((e=>"bytes"+e)).join(" ")+" ",o=t.map((e=>"uint"+e)).join(" ")+" ",g=t.map((e=>"int"+e)).join(" ")+" ",M=[].concat.apply([],t.map((e=>r.map((a=>e+"x"+a))))),p={ +keyword:"var bool string int uint "+g+o+"byte bytes "+l+"fixed ufixed "+M.map((e=>"fixed"+e)).join(" ")+" "+M.map((e=>"ufixed"+e)).join(" ")+" enum struct mapping address new delete if else for while continue break return throw emit try catch revert unchecked _ function modifier event constructor fallback receive error virtual override constant immutable anonymous indexed storage memory calldata external public internal payable pure view private returns import from as using pragma contract interface library is abstract type assembly", +literal:"true false wei gwei szabo finney ether seconds minutes hours days weeks years", +built_in:"self this super selfdestruct suicide now msg block tx abi blockhash gasleft assert require Error Panic sha3 sha256 keccak256 ripemd160 ecrecover addmod mulmod log0 log1 log2 log3 log4" +},O={className:"operator",begin:/[+\-!~*\/%<>&^|=]/ +},C=/[A-Za-z_$][A-Za-z_$0-9]*/,N={className:"params",begin:/\(/,end:/\)/, +excludeBegin:!0,excludeEnd:!0,lexemes:C,keywords:p, +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,b,"self"]},f={ +begin:/\.\s*/,end:/[^A-Za-z0-9$_\.]/,excludeBegin:!0,excludeEnd:!0,keywords:{ +built_in:"gas value selector address length push pop send transfer call callcode delegatecall staticcall balance code codehash wrap unwrap name creationCode runtimeCode interfaceId min max" +},relevance:2},y=e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/, +lexemes:C,keywords:p}),w={className:"built_in", +begin:(E()?"(? ONLY USDC is supported + +USDC <--(spot market)--> sUSDC <--(spot market)--> USDx + +### What is **Collateral Unwinding**? + +#### Flashloan Utility Flow + +1. **Request Flash Loans (Aave):** Borrow USDC to access liquidity without posting collateral. +2. **Zap into USDx (Synthetix Spot Market):** Use the borrowed funds to zap into USDx. +3. **Burn USDx & Repay Debt (Synthetix Core):** Repay Synthetix debt by burning USDx. +4. **Withdraw and Unwrap Collateral (Synthetix Spot Market):** Withdraw margin (e.g., sETH) and convert it back to underlying assets (e.g., WETH). +5. **Swap (Uniswap):** Exchange collateral assets (like WETH) for USDC to repay the flash loan. +6. **Flash Loan Repayment (Aave):** The USDC loan, including the premium, is repaid to Aave. +7. **Send Remaining Collateral (Synthetix):** Any surplus collateral is returned to the user. + +## Key Features + +- Zap via Synthetix +- Wrap & Unwrap Collateral via Synthetix +- Buy & Sell via Synthetix +- Unwind Collateral via Synthetix, Aave, and Uniswap +- Burn Debt via Synthetix +- Withdraw Perp Collateral via Synthetix +- Swap via Uniswap + +## Build and Test + +### Build and Run + +1. **Build the project** + ```bash + forge build + ``` + +2. **Run tests** + ```bash + forge test + ``` + +## Deployment + +- See the `deployments/` folder for Arbitrum and Base deployments. + +## Audits + +- See the `audits/` folder for Audit reports. diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 0000000..5ae87ee --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,16 @@ +# Summary +- [Home](README.md) +# src + - [❱ interfaces](src/interfaces/README.md) + - [IPool](src/interfaces/IAave.sol/interface.IPool.md) + - [IERC20](src/interfaces/IERC20.sol/interface.IERC20.md) + - [ISpotMarket](src/interfaces/ISynthetix.sol/interface.ISpotMarket.md) + - [IPerpsMarket](src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md) + - [IRouter](src/interfaces/IUniswap.sol/interface.IRouter.md) + - [IQuoter](src/interfaces/IUniswap.sol/interface.IQuoter.md) + - [❱ utils](src/utils/README.md) + - [Errors](src/utils/Errors.sol/contract.Errors.md) + - [Flush](src/utils/Flush.sol/contract.Flush.md) + - [Reentrancy](src/utils/Reentrancy.sol/contract.Reentrancy.md) + - [SafeERC20](src/utils/SafeTransferERC20.sol/library.SafeERC20.md) + - [Zap](src/Zap.sol/contract.Zap.md) diff --git a/docs/src/src/README.md b/docs/src/src/README.md new file mode 100644 index 0000000..73e089e --- /dev/null +++ b/docs/src/src/README.md @@ -0,0 +1,6 @@ + + +# Contents +- [interfaces](/src/interfaces) +- [utils](/src/utils) +- [Zap](Zap.sol/contract.Zap.md) diff --git a/docs/src/src/Zap.sol/contract.Zap.md b/docs/src/src/Zap.sol/contract.Zap.md new file mode 100644 index 0000000..4db7022 --- /dev/null +++ b/docs/src/src/Zap.sol/contract.Zap.md @@ -0,0 +1,904 @@ +# Zap +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/Zap.sol) + +**Inherits:** +[Reentrancy](/src/utils/Reentrancy.sol/contract.Reentrancy.md), [Errors](/src/utils/Errors.sol/contract.Errors.md) + +**Authors:** +@jaredborders, @flocqst, @barrasso, @moss-eth + +*idle token balances are not safe* + +*intended for standalone use; do not inherit* + + +## State Variables +### USDC + +```solidity +address public immutable USDC; +``` + + +### MODIFY_PERMISSION + +```solidity +bytes32 public constant MODIFY_PERMISSION = "PERPS_MODIFY_COLLATERAL"; +``` + + +### BURN_PERMISSION + +```solidity +bytes32 public constant BURN_PERMISSION = "BURN"; +``` + + +### USDX_ID + +```solidity +uint128 public immutable USDX_ID; +``` + + +### USDX + +```solidity +address public immutable USDX; +``` + + +### SPOT_MARKET + +```solidity +address public immutable SPOT_MARKET; +``` + + +### PERPS_MARKET + +```solidity +address public immutable PERPS_MARKET; +``` + + +### REFERRER + +```solidity +address public immutable REFERRER; +``` + + +### SUSDC_SPOT_ID + +```solidity +uint128 public immutable SUSDC_SPOT_ID; +``` + + +### REFERRAL_CODE + +```solidity +uint16 public constant REFERRAL_CODE = 0; +``` + + +### AAVE + +```solidity +address public immutable AAVE; +``` + + +### FEE_TIER + +```solidity +uint24 public constant FEE_TIER = 3000; +``` + + +### ROUTER + +```solidity +address public immutable ROUTER; +``` + + +### QUOTER + +```solidity +address public immutable QUOTER; +``` + + +## Functions +### constructor + + +```solidity +constructor( + address _usdc, + address _usdx, + address _spotMarket, + address _perpsMarket, + address _referrer, + uint128 _susdcSpotId, + address _aave, + address _router, + address _quoter +); +``` + +### isAuthorized + +validate caller is authorized to modify synthetix perp position + + +```solidity +modifier isAuthorized(uint128 _accountId); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_accountId`|`uint128`|synthetix perp market account id| + + +### onlyAave + +validate caller is Aave lending pool + + +```solidity +modifier onlyAave(); +``` + +### zapIn + +zap USDC into USDx + +*caller must grant USDC allowance to this contract* + + +```solidity +function zapIn( + uint256 _amount, + uint256 _tolerance, + address _receiver +) + external + returns (uint256 zapped); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_amount`|`uint256`|amount of USDC to zap| +|`_tolerance`|`uint256`|acceptable slippage for wrapping and selling| +|`_receiver`|`address`|address to receive USDx| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`zapped`|`uint256`|amount of USDx received| + + +### _zapIn + +*allowance is assumed* + +*following execution, this contract will hold the zapped USDx* + + +```solidity +function _zapIn( + uint256 _amount, + uint256 _tolerance +) + internal + returns (uint256 zapped); +``` + +### zapOut + +zap USDx into USDC + +*caller must grant USDx allowance to this contract* + + +```solidity +function zapOut( + uint256 _amount, + uint256 _tolerance, + address _receiver +) + external + returns (uint256 zapped); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_amount`|`uint256`|amount of USDx to zap| +|`_tolerance`|`uint256`|acceptable slippage for buying and unwrapping| +|`_receiver`|`address`|address to receive USDC| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`zapped`|`uint256`|amount of USDC received| + + +### _zapOut + +*allowance is assumed* + +*following execution, this contract will hold the zapped USDC* + + +```solidity +function _zapOut( + uint256 _amount, + uint256 _tolerance +) + internal + returns (uint256 zapped); +``` + +### wrap + +wrap collateral via synthetix spot market + +*caller must grant token allowance to this contract* + + +```solidity +function wrap( + address _token, + uint128 _synthId, + uint256 _amount, + uint256 _tolerance, + address _receiver +) + external + returns (uint256 wrapped); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_token`|`address`|address of token to wrap| +|`_synthId`|`uint128`|synthetix market id of synth to wrap into| +|`_amount`|`uint256`|amount of token to wrap| +|`_tolerance`|`uint256`|acceptable slippage for wrapping| +|`_receiver`|`address`|address to receive wrapped synth| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`wrapped`|`uint256`|amount of synth received| + + +### _wrap + +*allowance is assumed* + +*following execution, this contract will hold the wrapped synth* + + +```solidity +function _wrap( + address _token, + uint128 _synthId, + uint256 _amount, + uint256 _tolerance +) + internal + returns (uint256 wrapped); +``` + +### unwrap + +unwrap collateral via synthetix spot market + +*caller must grant synth allowance to this contract* + + +```solidity +function unwrap( + address _token, + uint128 _synthId, + uint256 _amount, + uint256 _tolerance, + address _receiver +) + external + returns (uint256 unwrapped); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_token`|`address`|address of token to unwrap into| +|`_synthId`|`uint128`|synthetix market id of synth to unwrap| +|`_amount`|`uint256`|amount of synth to unwrap| +|`_tolerance`|`uint256`|acceptable slippage for unwrapping| +|`_receiver`|`address`|address to receive unwrapped token| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`unwrapped`|`uint256`|amount of token received| + + +### _unwrap + +*allowance is assumed* + +*following execution, this contract will hold the unwrapped token* + + +```solidity +function _unwrap( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance +) + private + returns (uint256 unwrapped); +``` + +### buy + +buy synth via synthetix spot market + +*caller must grant USDX allowance to this contract* + + +```solidity +function buy( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance, + address _receiver +) + external + returns (uint256 received, address synth); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_synthId`|`uint128`|synthetix market id of synth to buy| +|`_amount`|`uint256`|amount of USDX to spend| +|`_tolerance`|`uint256`|acceptable slippage for buying| +|`_receiver`|`address`|address to receive synth| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`received`|`uint256`|amount of synth| +|`synth`|`address`|| + + +### _buy + +*allowance is assumed* + +*following execution, this contract will hold the bought synth* + + +```solidity +function _buy( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance +) + internal + returns (uint256 received); +``` + +### sell + +sell synth via synthetix spot market + +*caller must grant synth allowance to this contract* + + +```solidity +function sell( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance, + address _receiver +) + external + returns (uint256 received); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_synthId`|`uint128`|synthetix market id of synth to sell| +|`_amount`|`uint256`|amount of synth to sell| +|`_tolerance`|`uint256`|acceptable slippage for selling| +|`_receiver`|`address`|address to receive USDX| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`received`|`uint256`|amount of USDX| + + +### _sell + +*allowance is assumed* + +*following execution, this contract will hold the sold USDX* + + +```solidity +function _sell( + uint128 _synthId, + uint256 _amount, + uint256 _tolerance +) + internal + returns (uint256 received); +``` + +### unwind + +unwind synthetix perp position collateral + +*caller must grant USDC allowance to this contract* + + +```solidity +function unwind( + uint128 _accountId, + uint128 _collateralId, + uint256 _collateralAmount, + address _collateral, + bytes memory _path, + uint256 _zapTolerance, + uint256 _unwrapTolerance, + uint256 _swapTolerance, + address _receiver +) + external + isAuthorized(_accountId) + requireStage(Stage.UNSET); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_accountId`|`uint128`|synthetix perp market account id| +|`_collateralId`|`uint128`|synthetix market id of collateral| +|`_collateralAmount`|`uint256`|amount of collateral to unwind| +|`_collateral`|`address`|address of collateral to unwind| +|`_path`|`bytes`|Uniswap swap path encoded in reverse order| +|`_zapTolerance`|`uint256`|acceptable slippage for zapping| +|`_unwrapTolerance`|`uint256`|acceptable slippage for unwrapping| +|`_swapTolerance`|`uint256`|acceptable slippage for swapping| +|`_receiver`|`address`|address to receive unwound collateral| + + +### executeOperation + +flashloan callback function + +*caller must be the Aave lending pool* + + +```solidity +function executeOperation( + address, + uint256 _flashloan, + uint256 _premium, + address, + bytes calldata _params +) + external + onlyAave + requireStage(Stage.LEVEL1) + returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|``|`address`|| +|`_flashloan`|`uint256`|amount of USDC flashloaned from Aave| +|`_premium`|`uint256`|amount of USDC premium owed to Aave| +|``|`address`|| +|`_params`|`bytes`|encoded parameters for unwinding synthetix perp position| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|bool representing successful execution| + + +### _unwind + +*unwinds synthetix perp position collateral* + + +```solidity +function _unwind( + uint256 _flashloan, + uint256 _premium, + bytes calldata _params +) + internal + requireStage(Stage.LEVEL2) + returns (uint256 unwound); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_flashloan`|`uint256`|amount of USDC flashloaned from Aave| +|`_premium`|`uint256`|amount of USDC premium owed to Aave| +|`_params`|`bytes`|encoded parameters for unwinding synthetix perp position| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`unwound`|`uint256`|amount of collateral| + + +### _approximateLoanNeeded + +approximate USDC needed to unwind synthetix perp position + +*given the USDC buffer, an amount of USDx +necessarily less than the buffer will remain (<$1); +this amount is captured by the protocol* + + +```solidity +function _approximateLoanNeeded(uint128 _accountId) + internal + view + returns (uint256 amount); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_accountId`|`uint128`|synthetix perp market account id| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`amount`|`uint256`|of USDC needed| + + +### burn + +burn USDx to pay off synthetix perp position debt + +*scale loan amount accordingly* + +*barring exceptional circumstances, +a 1 USD buffer is sufficient to circumvent +precision loss* + +*caller must grant USDX allowance to this contract* + +*excess USDx will be returned to the caller* + + +```solidity +function burn( + uint256 _amount, + uint128 _accountId +) + external + returns (uint256 remaining); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_amount`|`uint256`|amount of USDx to burn| +|`_accountId`|`uint128`|synthetix perp market account id| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`remaining`|`uint256`|amount of USDx returned to the caller| + + +### _burn + +*allowance is assumed* + +*following execution, this contract will hold any excess USDx* + + +```solidity +function _burn(uint256 _amount, uint128 _accountId) internal; +``` + +### withdraw + +withdraw collateral from synthetix perp position + + +```solidity +function withdraw( + uint128 _synthId, + uint256 _amount, + uint128 _accountId, + address _receiver +) + external + isAuthorized(_accountId); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_synthId`|`uint128`|synthetix market id of collateral| +|`_amount`|`uint256`|amount of collateral to withdraw| +|`_accountId`|`uint128`|synthetix perp market account id| +|`_receiver`|`address`|address to receive collateral| + + +### _withdraw + +*following execution, this contract will hold the withdrawn +collateral* + + +```solidity +function _withdraw( + uint128 _synthId, + uint256 _amount, + uint128 _accountId +) + internal; +``` + +### quoteSwapFor + +query amount required to receive a specific amount of token + +*this is the QuoterV1 interface* + +*_path MUST be encoded backwards for `exactOutput`* + +*quoting is NOT gas efficient and should NOT be called on chain* + + +```solidity +function quoteSwapFor( + bytes memory _path, + uint256 _amountOut +) + external + returns ( + uint256 amountIn, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_path`|`bytes`|Uniswap swap path encoded in reverse order| +|`_amountOut`|`uint256`|is the desired output amount| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`amountIn`|`uint256`|required as the input for the swap in order| +|`sqrtPriceX96AfterList`|`uint160[]`|| +|`initializedTicksCrossedList`|`uint32[]`|| +|`gasEstimate`|`uint256`|| + + +### quoteSwapWith + +query amount received for a specific amount of token to spend + +*this is the QuoterV1 interface* + +*_path MUST be encoded in order for `exactInput`* + +*quoting is NOT gas efficient and should NOT be called on chain* + + +```solidity +function quoteSwapWith( + bytes memory _path, + uint256 _amountIn +) + external + returns ( + uint256 amountOut, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_path`|`bytes`|Uniswap swap path encoded in order| +|`_amountIn`|`uint256`|is the input amount to spendp| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`amountOut`|`uint256`|received as the output for the swap in order| +|`sqrtPriceX96AfterList`|`uint160[]`|| +|`initializedTicksCrossedList`|`uint32[]`|| +|`gasEstimate`|`uint256`|| + + +### swapFor + +swap a tolerable amount of tokens for a specific amount of USDC + +*_path MUST be encoded backwards for `exactOutput`* + +*caller must grant token allowance to this contract* + +*any excess token not spent will be returned to the caller* + + +```solidity +function swapFor( + address _from, + bytes memory _path, + uint256 _amount, + uint256 _tolerance, + address _receiver +) + external + returns (uint256 deducted); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_from`|`address`|address of token to swap| +|`_path`|`bytes`|uniswap swap path encoded in reverse order| +|`_amount`|`uint256`|amount of USDC to receive in return| +|`_tolerance`|`uint256`|or tolerable amount of token to spend| +|`_receiver`|`address`|address to receive USDC| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`deducted`|`uint256`|amount of incoming token; i.e., amount spent| + + +### _swapFor + +*allowance is assumed* + +*following execution, this contract will hold the swapped USDC* + + +```solidity +function _swapFor( + address _from, + bytes memory _path, + uint256 _amount, + uint256 _tolerance +) + internal + returns (uint256 deducted); +``` + +### swapWith + +swap a specific amount of tokens for a tolerable amount of USDC + +*_path MUST be encoded in order for `exactInput`* + +*caller must grant token allowance to this contract* + + +```solidity +function swapWith( + address _from, + bytes memory _path, + uint256 _amount, + uint256 _tolerance, + address _receiver +) + external + returns (uint256 received); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_from`|`address`|address of token to swap| +|`_path`|`bytes`|uniswap swap path encoded in order| +|`_amount`|`uint256`|of token to swap| +|`_tolerance`|`uint256`|tolerable amount of USDC to receive specified with 6 decimals| +|`_receiver`|`address`|address to receive USDC| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`received`|`uint256`|amount of USDC| + + +### _swapWith + +*allowance is assumed* + +*following execution, this contract will hold the swapped USDC* + + +```solidity +function _swapWith( + address _from, + bytes memory _path, + uint256 _amount, + uint256 _tolerance +) + internal + returns (uint256 received); +``` + +### _pull + +*pull tokens from a sender* + + +```solidity +function _pull(address _token, address _from, uint256 _amount) internal; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_token`|`address`|address of token to pull| +|`_from`|`address`|address of sender| +|`_amount`|`uint256`|amount of token to pull| + + +### _push + +*push tokens to a receiver* + + +```solidity +function _push(address _token, address _receiver, uint256 _amount) internal; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_token`|`address`|address of token to push| +|`_receiver`|`address`|address of receiver| +|`_amount`|`uint256`|amount of token to push| + + diff --git a/docs/src/src/interfaces/IAave.sol/interface.IPool.md b/docs/src/src/interfaces/IAave.sol/interface.IPool.md new file mode 100644 index 0000000..e67140b --- /dev/null +++ b/docs/src/src/interfaces/IAave.sol/interface.IPool.md @@ -0,0 +1,19 @@ +# IPool +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/IAave.sol) + + +## Functions +### flashLoanSimple + + +```solidity +function flashLoanSimple( + address receiverAddress, + address asset, + uint256 amount, + bytes calldata params, + uint16 referralCode +) + external; +``` + diff --git a/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md b/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md new file mode 100644 index 0000000..8174935 --- /dev/null +++ b/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md @@ -0,0 +1,59 @@ +# IERC20 +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/IERC20.sol) + + +## Functions +### decimals + + +```solidity +function decimals() external view returns (uint8); +``` + +### balanceOf + + +```solidity +function balanceOf(address account) external view returns (uint256); +``` + +### allowance + + +```solidity +function allowance( + address owner, + address spender +) + external + view + returns (uint256); +``` + +### transfer + + +```solidity +function transfer(address to, uint256 amount) external returns (bool); +``` + +### approve + + +```solidity +function approve(address spender, uint256 amount) external returns (bool); +``` + +### transferFrom + + +```solidity +function transferFrom( + address from, + address to, + uint256 amount +) + external + returns (bool); +``` + diff --git a/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md b/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md new file mode 100644 index 0000000..9a98f1e --- /dev/null +++ b/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md @@ -0,0 +1,52 @@ +# IPerpsMarket +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/ISynthetix.sol) + + +## Functions +### modifyCollateral + + +```solidity +function modifyCollateral( + uint128 accountId, + uint128 synthMarketId, + int256 amountDelta +) + external; +``` + +### renouncePermission + + +```solidity +function renouncePermission(uint128 accountId, bytes32 permission) external; +``` + +### isAuthorized + + +```solidity +function isAuthorized( + uint128 accountId, + bytes32 permission, + address target +) + external + view + returns (bool isAuthorized); +``` + +### payDebt + + +```solidity +function payDebt(uint128 accountId, uint256 amount) external; +``` + +### debt + + +```solidity +function debt(uint128 accountId) external view returns (uint256 accountDebt); +``` + diff --git a/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md b/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md new file mode 100644 index 0000000..7a4f5ca --- /dev/null +++ b/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md @@ -0,0 +1,81 @@ +# ISpotMarket +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/ISynthetix.sol) + + +## Functions +### getSynth + + +```solidity +function getSynth(uint128 marketId) + external + view + returns (address synthAddress); +``` + +### wrap + + +```solidity +function wrap( + uint128 marketId, + uint256 wrapAmount, + uint256 minAmountReceived +) + external + returns (uint256 amountToMint, Data memory fees); +``` + +### unwrap + + +```solidity +function unwrap( + uint128 marketId, + uint256 unwrapAmount, + uint256 minAmountReceived +) + external + returns (uint256 returnCollateralAmount, Data memory fees); +``` + +### buy + + +```solidity +function buy( + uint128 marketId, + uint256 usdAmount, + uint256 minAmountReceived, + address referrer +) + external + returns (uint256 synthAmount, Data memory fees); +``` + +### sell + + +```solidity +function sell( + uint128 marketId, + uint256 synthAmount, + uint256 minUsdAmount, + address referrer +) + external + returns (uint256 usdAmountReceived, Data memory fees); +``` + +## Structs +### Data + +```solidity +struct Data { + uint256 fixedFees; + uint256 utilizationFees; + int256 skewFees; + int256 wrapperFees; +} +``` + diff --git a/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md b/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md new file mode 100644 index 0000000..9a7d30c --- /dev/null +++ b/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md @@ -0,0 +1,77 @@ +# IQuoter +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/IUniswap.sol) + + +## Functions +### quoteExactInput + +Returns the amount out received for a given exact input swap +without executing the swap + + +```solidity +function quoteExactInput( + bytes memory path, + uint256 amountIn +) + external + returns ( + uint256 amountOut, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`path`|`bytes`|The path of the swap, i.e. each token pair and the pool fee| +|`amountIn`|`uint256`|The amount of the first token to swap| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`amountOut`|`uint256`|The amount of the last token that would be received| +|`sqrtPriceX96AfterList`|`uint160[]`|List of the sqrt price after the swap for each pool in the path| +|`initializedTicksCrossedList`|`uint32[]`|List of the initialized ticks that the swap crossed for each pool in the path| +|`gasEstimate`|`uint256`|The estimate of the gas that the swap consumes| + + +### quoteExactOutput + +Returns the amount in required for a given exact output swap +without executing the swap + + +```solidity +function quoteExactOutput( + bytes memory path, + uint256 amountOut +) + external + returns ( + uint256 amountIn, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`path`|`bytes`|The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order| +|`amountOut`|`uint256`|The amount of the last token to receive| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`amountIn`|`uint256`|The amount of first token required to be paid| +|`sqrtPriceX96AfterList`|`uint160[]`|List of the sqrt price after the swap for each pool in the path| +|`initializedTicksCrossedList`|`uint32[]`|List of the initialized ticks that the swap crossed for each pool in the path| +|`gasEstimate`|`uint256`|The estimate of the gas that the swap consumes| + + diff --git a/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md b/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md new file mode 100644 index 0000000..97f21fc --- /dev/null +++ b/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md @@ -0,0 +1,78 @@ +# IRouter +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/IUniswap.sol) + + +## Functions +### exactInput + +Swaps `amountIn` of one token for as much as possible of another +along the specified path + + +```solidity +function exactInput(ExactInputParams calldata params) + external + payable + returns (uint256 amountOut); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`ExactInputParams`|The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`amountOut`|`uint256`|The amount of the received token| + + +### exactOutput + +Swaps as little as possible of one token for `amountOut` of +another along the specified path (reversed) + + +```solidity +function exactOutput(ExactOutputParams calldata params) + external + payable + returns (uint256 amountIn); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`ExactOutputParams`|The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|`amountIn`|`uint256`|The amount of the input token| + + +## Structs +### ExactInputParams + +```solidity +struct ExactInputParams { + bytes path; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; +} +``` + +### ExactOutputParams + +```solidity +struct ExactOutputParams { + bytes path; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; +} +``` + diff --git a/docs/src/src/interfaces/README.md b/docs/src/src/interfaces/README.md new file mode 100644 index 0000000..87a90dd --- /dev/null +++ b/docs/src/src/interfaces/README.md @@ -0,0 +1,9 @@ + + +# Contents +- [IPool](IAave.sol/interface.IPool.md) +- [IERC20](IERC20.sol/interface.IERC20.md) +- [ISpotMarket](ISynthetix.sol/interface.ISpotMarket.md) +- [IPerpsMarket](ISynthetix.sol/interface.IPerpsMarket.md) +- [IRouter](IUniswap.sol/interface.IRouter.md) +- [IQuoter](IUniswap.sol/interface.IQuoter.md) diff --git a/docs/src/src/utils/Errors.sol/contract.Errors.md b/docs/src/src/utils/Errors.sol/contract.Errors.md new file mode 100644 index 0000000..eb24825 --- /dev/null +++ b/docs/src/src/utils/Errors.sol/contract.Errors.md @@ -0,0 +1,136 @@ +# Errors +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/utils/Errors.sol) + +**Author:** +@jaredborders + + +## Errors +### WrapFailed +thrown when a wrap operation fails + + +```solidity +error WrapFailed(string reason); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`reason`|`string`|string for the failure| + +### UnwrapFailed +thrown when an unwrap operation fails + + +```solidity +error UnwrapFailed(string reason); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`reason`|`string`|string for the failure| + +### BuyFailed +thrown when a buy operation fails + + +```solidity +error BuyFailed(string reason); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`reason`|`string`|string for the failure| + +### SellFailed +thrown when a sell operation fails + + +```solidity +error SellFailed(string reason); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`reason`|`string`|string for the failure| + +### SwapFailed +thrown when a swap operation fails + + +```solidity +error SwapFailed(string reason); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`reason`|`string`|string for the failure| + +### NotPermitted +thrown when operation is not permitted + + +```solidity +error NotPermitted(); +``` + +### ReentrancyGuardReentrantCall +Unauthorized reentrant call. + + +```solidity +error ReentrancyGuardReentrantCall(); +``` + +### PullFailed +thrown when a pull operation fails + + +```solidity +error PullFailed(bytes reason); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`reason`|`bytes`|data for the failure| + +### PushFailed +thrown when a push operation fails + + +```solidity +error PushFailed(bytes reason); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`reason`|`bytes`|data for the failure| + +### OnlyAave +thrown when caller is not Aave pool address + + +```solidity +error OnlyAave(address caller); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`caller`|`address`|address of the msg.sender| + diff --git a/docs/src/src/utils/Flush.sol/contract.Flush.md b/docs/src/src/utils/Flush.sol/contract.Flush.md new file mode 100644 index 0000000..a1ec13f --- /dev/null +++ b/docs/src/src/utils/Flush.sol/contract.Flush.md @@ -0,0 +1,73 @@ +# Flush +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/utils/Flush.sol) + +**Author:** +@jaredborders + + +## State Variables +### PLUMBER + +```solidity +address public PLUMBER; +``` + + +## Functions +### constructor + + +```solidity +constructor(address _plumber); +``` + +### flush + +flush dust out of the contract + + +```solidity +function flush(address _token) external; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_token`|`address`|address of token to flush| + + +### designatePlumber + +designate a new plumber + +*zero address can be used to remove flush capability* + + +```solidity +function designatePlumber(address _newPlumber) external; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_newPlumber`|`address`|address of new plumber| + + +## Events +### PlumberDesignated +emitted when a new plumber is designated + + +```solidity +event PlumberDesignated(address plumber); +``` + +## Errors +### OnlyPlumber +thrown when caller is not the plumber + + +```solidity +error OnlyPlumber(); +``` + diff --git a/docs/src/src/utils/README.md b/docs/src/src/utils/README.md new file mode 100644 index 0000000..a100808 --- /dev/null +++ b/docs/src/src/utils/README.md @@ -0,0 +1,7 @@ + + +# Contents +- [Errors](Errors.sol/contract.Errors.md) +- [Flush](Flush.sol/contract.Flush.md) +- [Reentrancy](Reentrancy.sol/contract.Reentrancy.md) +- [SafeERC20](SafeTransferERC20.sol/library.SafeERC20.md) diff --git a/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md b/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md new file mode 100644 index 0000000..b6273f6 --- /dev/null +++ b/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md @@ -0,0 +1,71 @@ +# Reentrancy +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/utils/Reentrancy.sol) + +**Authors:** +@moss-eth, @jaredborders + + +## State Variables +### stage +current stage of execution + + +```solidity +Stage internal stage; +``` + + +## Functions +### requireStage + +validate current stage of execution is as expected + + +```solidity +modifier requireStage(Stage expected); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`expected`|`Stage`|stage of execution| + + +### _requireStage + + +```solidity +function _requireStage(Stage _expected) internal view; +``` + +## Errors +### ReentrancyDetected +thrown when stage of execution is not expected + + +```solidity +error ReentrancyDetected(Stage actual, Stage expected); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`actual`|`Stage`|current stage of execution| +|`expected`|`Stage`|expected stage of execution| + +## Enums +### Stage +enumerated stages of execution + +*each stage denotes a different level of protection* + + +```solidity +enum Stage { + UNSET, + LEVEL1, + LEVEL2 +} +``` + diff --git a/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md b/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md new file mode 100644 index 0000000..b598567 --- /dev/null +++ b/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md @@ -0,0 +1,71 @@ +# SafeERC20 +[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/utils/SafeTransferERC20.sol) + +*Wrappers around ERC-20 operations that throw on failure (when the token +contract returns false). Tokens that return no value (and instead revert or +throw on failure) are also supported, non-reverting calls are assumed to be +successful. +To use this library you can add a `using SafeERC20 for IERC20;` statement to +your contract, +which allows you to call the safe operations as `token.safeTransfer(...)`, +etc.* + + +## Functions +### safeTransfer + +*Transfer `value` amount of `token` from the calling contract to +`to`. If `token` returns no value, +non-reverting calls are assumed to be successful.* + + +```solidity +function safeTransfer(IERC20 token, address to, uint256 value) internal; +``` + +### safeTransferFrom + +*Transfer `value` amount of `token` from `from` to `to`, spending the +approval given by `from` to the +calling contract. If `token` returns no value, non-reverting calls are +assumed to be successful.* + + +```solidity +function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value +) + internal; +``` + +### _callOptionalReturn + +*Imitates a Solidity high-level call (i.e. a regular function call to +a contract), relaxing the requirement +on the return value: the return value is optional (but if data is +returned, it must not be false).* + + +```solidity +function _callOptionalReturn(IERC20 token, bytes memory data) private; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`token`|`IERC20`|The token targeted by the call.| +|`data`|`bytes`|The call data (encoded using abi.encode or one of its variants). This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.| + + +## Errors +### SafeERC20FailedOperation +*An operation with an ERC-20 token failed.* + + +```solidity +error SafeERC20FailedOperation(address token); +``` + From 3639a33548aabf1c46216df5905f78b7c49e363b Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 17 Oct 2024 12:19:55 -0700 Subject: [PATCH 110/129] =?UTF-8?q?=E2=9C=85=20Swap=20with=20tests=20(#52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✅ swap with tests * ✨ removed double factory declaration --- test/Swap.with.t.sol | 82 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/test/Swap.with.t.sol b/test/Swap.with.t.sol index ddd5d35..d552f79 100644 --- a/test/Swap.with.t.sol +++ b/test/Swap.with.t.sol @@ -5,8 +5,10 @@ import { Bootstrap, Constants, IERC20, + IFactory, IPerpsMarket, IPool, + IRouter, ISpotMarket, Test, Zap @@ -15,10 +17,86 @@ import { contract SwapWithTest is Bootstrap { /// @custom:todo - function test_swap_with_base() public base {} + function test_swap_with_base(uint8 percentage) public base { + vm.assume(percentage < 95 && percentage > 0); + + uint256 tolerance = type(uint256).max; + _spin(ACTOR, weth, tolerance, address(zap)); + + address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( + address(weth), address(usdc), zap.FEE_TIER() + ); + uint256 depth = usdc.balanceOf(pool); + uint256 amount = depth * (percentage / 100); + + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(weth.balanceOf(ACTOR), tolerance); + + vm.startPrank(ACTOR); + + if (amount == 0) { + vm.expectRevert(); + } + + zap.swapWith({ + _from: address(weth), + _path: abi.encodePacked(address(weth), FEE_30, address(usdx)), + _amount: amount, + _tolerance: tolerance, + _receiver: ACTOR + }); + + assertTrue( + amount > 1e6 + ? usdc.balanceOf(ACTOR) < 0 + : usdc.balanceOf(ACTOR) == 0 + ); + assertLe(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); + + vm.stopPrank(); + } /// @custom:todo - function test_swap_with_arbitrum() public arbitrum {} + function test_swap_with_arbitrum(uint8 percentage) public arbitrum { + vm.assume(percentage < 95 && percentage > 0); + + uint256 tolerance = type(uint256).max; + _spin(ACTOR, weth, tolerance, address(zap)); + + address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( + address(weth), address(usdc), zap.FEE_TIER() + ); + uint256 depth = usdc.balanceOf(pool); + uint256 amount = depth * (percentage / 100); + + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(weth.balanceOf(ACTOR), tolerance); + + vm.startPrank(ACTOR); + + if (amount == 0) { + vm.expectRevert(); + } + + zap.swapWith({ + _from: address(weth), + _path: abi.encodePacked(address(weth), FEE_30, address(usdc)), + _amount: amount, + _tolerance: tolerance, + _receiver: ACTOR + }); + + assertTrue( + amount > 1e6 + ? usdc.balanceOf(ACTOR) < 0 + : usdc.balanceOf(ACTOR) == 0 + ); + assertLe(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); + + vm.stopPrank(); + } /// @custom:todo function test_swap_with_arbitrum_sepolia() public arbitrum_sepolia {} From d761662fc88e8b0d370a7790c767a475cc5bb8bd Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 21 Oct 2024 07:35:55 -0700 Subject: [PATCH 111/129] =?UTF-8?q?=E2=9C=85=20Tolerance=20rename=20&=20mu?= =?UTF-8?q?ltihop=20tests=20(#51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 👷 SingleSwaps -> MultiHop WIP no tests * ✅ swapFor tests * ✨ added comment to prevent path gotcha * fixed quote fns * swap tests * fmt * 👷 added return vars back to quote fns * ✨ wasp review fixes * 👷 fixed IUniswap Quoter interface * 👷 fixed execute operation bytes decoding * tolerance rename wip * ✅ testing multihop wip * ✨ fmt * ✅ fixed arbies multihop test * ✨ swap with tolerance rename * 📚 doc update * finished tolerance rename --- docs/src/src/Zap.sol/contract.Zap.md | 62 ++++---- .../interfaces/IAave.sol/interface.IPool.md | 2 +- .../interfaces/IERC20.sol/interface.IERC20.md | 2 +- .../ISynthetix.sol/interface.IPerpsMarket.md | 2 +- .../ISynthetix.sol/interface.ISpotMarket.md | 2 +- .../IUniswap.sol/interface.IQuoter.md | 2 +- .../IUniswap.sol/interface.IRouter.md | 2 +- .../src/utils/Errors.sol/contract.Errors.md | 2 +- .../src/src/utils/Flush.sol/contract.Flush.md | 2 +- .../Reentrancy.sol/contract.Reentrancy.md | 2 +- .../library.SafeERC20.md | 2 +- src/Zap.sol | 133 ++++++++++-------- test/Buy.t.sol | 12 +- test/Sell.t.sol | 20 +-- test/Swap.for.t.sol | 86 +++++------ test/Swap.with.t.sol | 20 +-- test/Unwrap.t.sol | 16 +-- test/Wrap.t.sol | 8 +- test/Zap.in.t.sol | 8 +- test/Zap.out.t.sol | 8 +- test/utils/Constants.sol | 4 +- 21 files changed, 195 insertions(+), 202 deletions(-) diff --git a/docs/src/src/Zap.sol/contract.Zap.md b/docs/src/src/Zap.sol/contract.Zap.md index 4db7022..32aa944 100644 --- a/docs/src/src/Zap.sol/contract.Zap.md +++ b/docs/src/src/Zap.sol/contract.Zap.md @@ -1,5 +1,5 @@ # Zap -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/Zap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/Zap.sol) **Inherits:** [Reentrancy](/src/utils/Reentrancy.sol/contract.Reentrancy.md), [Errors](/src/utils/Errors.sol/contract.Errors.md) @@ -163,7 +163,7 @@ zap USDC into USDx ```solidity function zapIn( uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -174,7 +174,7 @@ function zapIn( |Name|Type|Description| |----|----|-----------| |`_amount`|`uint256`|amount of USDC to zap| -|`_tolerance`|`uint256`|acceptable slippage for wrapping and selling| +|`_minAmountOut`|`uint256`|acceptable slippage for wrapping and selling| |`_receiver`|`address`|address to receive USDx| **Returns** @@ -194,7 +194,7 @@ function zapIn( ```solidity function _zapIn( uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 zapped); @@ -210,7 +210,7 @@ zap USDx into USDC ```solidity function zapOut( uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -221,7 +221,7 @@ function zapOut( |Name|Type|Description| |----|----|-----------| |`_amount`|`uint256`|amount of USDx to zap| -|`_tolerance`|`uint256`|acceptable slippage for buying and unwrapping| +|`_minAmountOut`|`uint256`|acceptable slippage for buying and unwrapping| |`_receiver`|`address`|address to receive USDC| **Returns** @@ -241,7 +241,7 @@ function zapOut( ```solidity function _zapOut( uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 zapped); @@ -259,7 +259,7 @@ function wrap( address _token, uint128 _synthId, uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -272,7 +272,7 @@ function wrap( |`_token`|`address`|address of token to wrap| |`_synthId`|`uint128`|synthetix market id of synth to wrap into| |`_amount`|`uint256`|amount of token to wrap| -|`_tolerance`|`uint256`|acceptable slippage for wrapping| +|`_minAmountOut`|`uint256`|acceptable slippage for wrapping| |`_receiver`|`address`|address to receive wrapped synth| **Returns** @@ -294,7 +294,7 @@ function _wrap( address _token, uint128 _synthId, uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 wrapped); @@ -312,7 +312,7 @@ function unwrap( address _token, uint128 _synthId, uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -325,7 +325,7 @@ function unwrap( |`_token`|`address`|address of token to unwrap into| |`_synthId`|`uint128`|synthetix market id of synth to unwrap| |`_amount`|`uint256`|amount of synth to unwrap| -|`_tolerance`|`uint256`|acceptable slippage for unwrapping| +|`_minAmountOut`|`uint256`|acceptable slippage for unwrapping| |`_receiver`|`address`|address to receive unwrapped token| **Returns** @@ -346,7 +346,7 @@ function unwrap( function _unwrap( uint128 _synthId, uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) private returns (uint256 unwrapped); @@ -363,7 +363,7 @@ buy synth via synthetix spot market function buy( uint128 _synthId, uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -375,7 +375,7 @@ function buy( |----|----|-----------| |`_synthId`|`uint128`|synthetix market id of synth to buy| |`_amount`|`uint256`|amount of USDX to spend| -|`_tolerance`|`uint256`|acceptable slippage for buying| +|`_minAmountOut`|`uint256`|acceptable slippage for buying| |`_receiver`|`address`|address to receive synth| **Returns** @@ -397,7 +397,7 @@ function buy( function _buy( uint128 _synthId, uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 received); @@ -414,7 +414,7 @@ sell synth via synthetix spot market function sell( uint128 _synthId, uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -426,7 +426,7 @@ function sell( |----|----|-----------| |`_synthId`|`uint128`|synthetix market id of synth to sell| |`_amount`|`uint256`|amount of synth to sell| -|`_tolerance`|`uint256`|acceptable slippage for selling| +|`_minAmountOut`|`uint256`|acceptable slippage for selling| |`_receiver`|`address`|address to receive USDX| **Returns** @@ -447,7 +447,7 @@ function sell( function _sell( uint128 _synthId, uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 received); @@ -467,9 +467,9 @@ function unwind( uint256 _collateralAmount, address _collateral, bytes memory _path, - uint256 _zapTolerance, - uint256 _unwrapTolerance, - uint256 _swapTolerance, + uint256 _zapMinAmountOut, + uint256 _unwrapMinAmountOut, + uint256 _swapMaxAmountIn, address _receiver ) external @@ -485,9 +485,9 @@ function unwind( |`_collateralAmount`|`uint256`|amount of collateral to unwind| |`_collateral`|`address`|address of collateral to unwind| |`_path`|`bytes`|Uniswap swap path encoded in reverse order| -|`_zapTolerance`|`uint256`|acceptable slippage for zapping| -|`_unwrapTolerance`|`uint256`|acceptable slippage for unwrapping| -|`_swapTolerance`|`uint256`|acceptable slippage for swapping| +|`_zapMinAmountOut`|`uint256`|acceptable slippage for zapping| +|`_unwrapMinAmountOut`|`uint256`|acceptable slippage for unwrapping| +|`_swapMaxAmountIn`|`uint256`|acceptable slippage for swapping| |`_receiver`|`address`|address to receive unwound collateral| @@ -772,7 +772,7 @@ function swapFor( address _from, bytes memory _path, uint256 _amount, - uint256 _tolerance, + uint256 _maxAmountIn, address _receiver ) external @@ -785,7 +785,7 @@ function swapFor( |`_from`|`address`|address of token to swap| |`_path`|`bytes`|uniswap swap path encoded in reverse order| |`_amount`|`uint256`|amount of USDC to receive in return| -|`_tolerance`|`uint256`|or tolerable amount of token to spend| +|`_maxAmountIn`|`uint256`|max amount of token to spend| |`_receiver`|`address`|address to receive USDC| **Returns** @@ -807,7 +807,7 @@ function _swapFor( address _from, bytes memory _path, uint256 _amount, - uint256 _tolerance + uint256 _maxAmountIn ) internal returns (uint256 deducted); @@ -827,7 +827,7 @@ function swapWith( address _from, bytes memory _path, uint256 _amount, - uint256 _tolerance, + uint256 _amountOutMinimum, address _receiver ) external @@ -840,7 +840,7 @@ function swapWith( |`_from`|`address`|address of token to swap| |`_path`|`bytes`|uniswap swap path encoded in order| |`_amount`|`uint256`|of token to swap| -|`_tolerance`|`uint256`|tolerable amount of USDC to receive specified with 6 decimals| +|`_amountOutMinimum`|`uint256`|tolerable amount of USDC to receive specified with 6 decimals| |`_receiver`|`address`|address to receive USDC| **Returns** @@ -862,7 +862,7 @@ function _swapWith( address _from, bytes memory _path, uint256 _amount, - uint256 _tolerance + uint256 _amountOutMinimum ) internal returns (uint256 received); diff --git a/docs/src/src/interfaces/IAave.sol/interface.IPool.md b/docs/src/src/interfaces/IAave.sol/interface.IPool.md index e67140b..dcdd649 100644 --- a/docs/src/src/interfaces/IAave.sol/interface.IPool.md +++ b/docs/src/src/interfaces/IAave.sol/interface.IPool.md @@ -1,5 +1,5 @@ # IPool -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/IAave.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/IAave.sol) ## Functions diff --git a/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md b/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md index 8174935..696efb9 100644 --- a/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md +++ b/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md @@ -1,5 +1,5 @@ # IERC20 -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/IERC20.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/IERC20.sol) ## Functions diff --git a/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md b/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md index 9a98f1e..26b99c9 100644 --- a/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md +++ b/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md @@ -1,5 +1,5 @@ # IPerpsMarket -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/ISynthetix.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/ISynthetix.sol) ## Functions diff --git a/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md b/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md index 7a4f5ca..93bba4c 100644 --- a/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md +++ b/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md @@ -1,5 +1,5 @@ # ISpotMarket -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/ISynthetix.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/ISynthetix.sol) ## Functions diff --git a/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md b/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md index 9a7d30c..e9be6bf 100644 --- a/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md +++ b/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md @@ -1,5 +1,5 @@ # IQuoter -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/IUniswap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/IUniswap.sol) ## Functions diff --git a/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md b/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md index 97f21fc..844c9d2 100644 --- a/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md +++ b/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md @@ -1,5 +1,5 @@ # IRouter -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/interfaces/IUniswap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/IUniswap.sol) ## Functions diff --git a/docs/src/src/utils/Errors.sol/contract.Errors.md b/docs/src/src/utils/Errors.sol/contract.Errors.md index eb24825..f05a082 100644 --- a/docs/src/src/utils/Errors.sol/contract.Errors.md +++ b/docs/src/src/utils/Errors.sol/contract.Errors.md @@ -1,5 +1,5 @@ # Errors -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/utils/Errors.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/utils/Errors.sol) **Author:** @jaredborders diff --git a/docs/src/src/utils/Flush.sol/contract.Flush.md b/docs/src/src/utils/Flush.sol/contract.Flush.md index a1ec13f..7ba6cf2 100644 --- a/docs/src/src/utils/Flush.sol/contract.Flush.md +++ b/docs/src/src/utils/Flush.sol/contract.Flush.md @@ -1,5 +1,5 @@ # Flush -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/utils/Flush.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/utils/Flush.sol) **Author:** @jaredborders diff --git a/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md b/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md index b6273f6..de8444e 100644 --- a/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md +++ b/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md @@ -1,5 +1,5 @@ # Reentrancy -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/utils/Reentrancy.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/utils/Reentrancy.sol) **Authors:** @moss-eth, @jaredborders diff --git a/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md b/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md index b598567..e5fc33d 100644 --- a/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md +++ b/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md @@ -1,5 +1,5 @@ # SafeERC20 -[Git Source](https://github.com/moss-eth/zap/blob/35e517eceade43560c1eb54d47de1fc3aa949331/src/utils/SafeTransferERC20.sol) +[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/utils/SafeTransferERC20.sol) *Wrappers around ERC-20 operations that throw on failure (when the token contract returns false). Tokens that return no value (and instead revert or diff --git a/src/Zap.sol b/src/Zap.sol index 20c2f28..15e78a5 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -99,19 +99,19 @@ contract Zap is Reentrancy, Errors { /// @notice zap USDC into USDx /// @dev caller must grant USDC allowance to this contract /// @param _amount amount of USDC to zap - /// @param _tolerance acceptable slippage for wrapping and selling + /// @param _minAmountOut acceptable slippage for wrapping and selling /// @param _receiver address to receive USDx /// @return zapped amount of USDx received function zapIn( uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external returns (uint256 zapped) { _pull(USDC, msg.sender, _amount); - zapped = _zapIn(_amount, _tolerance); + zapped = _zapIn(_amount, _minAmountOut); _push(USDX, _receiver, zapped); } @@ -119,31 +119,31 @@ contract Zap is Reentrancy, Errors { /// @dev following execution, this contract will hold the zapped USDx function _zapIn( uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 zapped) { - zapped = _wrap(USDC, SUSDC_SPOT_ID, _amount, _tolerance); - zapped = _sell(SUSDC_SPOT_ID, zapped, _tolerance); + zapped = _wrap(USDC, SUSDC_SPOT_ID, _amount, _minAmountOut); + zapped = _sell(SUSDC_SPOT_ID, zapped, _minAmountOut); } /// @notice zap USDx into USDC /// @dev caller must grant USDx allowance to this contract /// @param _amount amount of USDx to zap - /// @param _tolerance acceptable slippage for buying and unwrapping + /// @param _minAmountOut acceptable slippage for buying and unwrapping /// @param _receiver address to receive USDC /// @return zapped amount of USDC received function zapOut( uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external returns (uint256 zapped) { _pull(USDX, msg.sender, _amount); - zapped = _zapOut(_amount, _tolerance); + zapped = _zapOut(_amount, _minAmountOut); _push(USDC, _receiver, zapped); } @@ -151,13 +151,13 @@ contract Zap is Reentrancy, Errors { /// @dev following execution, this contract will hold the zapped USDC function _zapOut( uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 zapped) { - zapped = _buy(SUSDC_SPOT_ID, _amount, _tolerance); - zapped = _unwrap(SUSDC_SPOT_ID, zapped, _tolerance); + zapped = _buy(SUSDC_SPOT_ID, _amount, _minAmountOut); + zapped = _unwrap(SUSDC_SPOT_ID, zapped, _minAmountOut); } /*////////////////////////////////////////////////////////////// @@ -170,21 +170,21 @@ contract Zap is Reentrancy, Errors { /// @param _token address of token to wrap /// @param _synthId synthetix market id of synth to wrap into /// @param _amount amount of token to wrap - /// @param _tolerance acceptable slippage for wrapping + /// @param _minAmountOut acceptable slippage for wrapping /// @param _receiver address to receive wrapped synth /// @return wrapped amount of synth received function wrap( address _token, uint128 _synthId, uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external returns (uint256 wrapped) { _pull(_token, msg.sender, _amount); - wrapped = _wrap(_token, _synthId, _amount, _tolerance); + wrapped = _wrap(_token, _synthId, _amount, _minAmountOut); _push(ISpotMarket(SPOT_MARKET).getSynth(_synthId), _receiver, wrapped); } @@ -194,7 +194,7 @@ contract Zap is Reentrancy, Errors { address _token, uint128 _synthId, uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 wrapped) @@ -203,7 +203,7 @@ contract Zap is Reentrancy, Errors { (wrapped,) = ISpotMarket(SPOT_MARKET).wrap({ marketId: _synthId, wrapAmount: _amount, - minAmountReceived: _tolerance + minAmountReceived: _minAmountOut }); } @@ -213,14 +213,14 @@ contract Zap is Reentrancy, Errors { /// @param _token address of token to unwrap into /// @param _synthId synthetix market id of synth to unwrap /// @param _amount amount of synth to unwrap - /// @param _tolerance acceptable slippage for unwrapping + /// @param _minAmountOut acceptable slippage for unwrapping /// @param _receiver address to receive unwrapped token /// @return unwrapped amount of token received function unwrap( address _token, uint128 _synthId, uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -228,7 +228,7 @@ contract Zap is Reentrancy, Errors { { address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); _pull(synth, msg.sender, _amount); - unwrapped = _unwrap(_synthId, _amount, _tolerance); + unwrapped = _unwrap(_synthId, _amount, _minAmountOut); _push(_token, _receiver, unwrapped); } @@ -237,7 +237,7 @@ contract Zap is Reentrancy, Errors { function _unwrap( uint128 _synthId, uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) private returns (uint256 unwrapped) @@ -247,7 +247,7 @@ contract Zap is Reentrancy, Errors { (unwrapped,) = ISpotMarket(SPOT_MARKET).unwrap({ marketId: _synthId, unwrapAmount: _amount, - minAmountReceived: _tolerance + minAmountReceived: _minAmountOut }); } @@ -259,13 +259,13 @@ contract Zap is Reentrancy, Errors { /// @dev caller must grant USDX allowance to this contract /// @param _synthId synthetix market id of synth to buy /// @param _amount amount of USDX to spend - /// @param _tolerance acceptable slippage for buying + /// @param _minAmountOut acceptable slippage for buying /// @param _receiver address to receive synth /// @return received amount of synth function buy( uint128 _synthId, uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -273,7 +273,7 @@ contract Zap is Reentrancy, Errors { { synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); _pull(USDX, msg.sender, _amount); - received = _buy(_synthId, _amount, _tolerance); + received = _buy(_synthId, _amount, _minAmountOut); _push(synth, _receiver, received); } @@ -282,7 +282,7 @@ contract Zap is Reentrancy, Errors { function _buy( uint128 _synthId, uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 received) @@ -291,7 +291,7 @@ contract Zap is Reentrancy, Errors { (received,) = ISpotMarket(SPOT_MARKET).buy({ marketId: _synthId, usdAmount: _amount, - minAmountReceived: _tolerance, + minAmountReceived: _minAmountOut, referrer: REFERRER }); } @@ -300,13 +300,13 @@ contract Zap is Reentrancy, Errors { /// @dev caller must grant synth allowance to this contract /// @param _synthId synthetix market id of synth to sell /// @param _amount amount of synth to sell - /// @param _tolerance acceptable slippage for selling + /// @param _minAmountOut acceptable slippage for selling /// @param _receiver address to receive USDX /// @return received amount of USDX function sell( uint128 _synthId, uint256 _amount, - uint256 _tolerance, + uint256 _minAmountOut, address _receiver ) external @@ -314,7 +314,7 @@ contract Zap is Reentrancy, Errors { { address synth = ISpotMarket(SPOT_MARKET).getSynth(_synthId); _pull(synth, msg.sender, _amount); - received = _sell(_synthId, _amount, _tolerance); + received = _sell(_synthId, _amount, _minAmountOut); _push(USDX, _receiver, received); } @@ -323,7 +323,7 @@ contract Zap is Reentrancy, Errors { function _sell( uint128 _synthId, uint256 _amount, - uint256 _tolerance + uint256 _minAmountOut ) internal returns (uint256 received) @@ -333,7 +333,7 @@ contract Zap is Reentrancy, Errors { (received,) = ISpotMarket(SPOT_MARKET).sell({ marketId: _synthId, synthAmount: _amount, - minUsdAmount: _tolerance, + minUsdAmount: _minAmountOut, referrer: REFERRER }); } @@ -350,9 +350,9 @@ contract Zap is Reentrancy, Errors { /// @param _collateralAmount amount of collateral to unwind /// @param _collateral address of collateral to unwind /// @param _path Uniswap swap path encoded in reverse order - /// @param _zapTolerance acceptable slippage for zapping - /// @param _unwrapTolerance acceptable slippage for unwrapping - /// @param _swapTolerance acceptable slippage for swapping + /// @param _zapMinAmountOut acceptable slippage for zapping + /// @param _unwrapMinAmountOut acceptable slippage for unwrapping + /// @param _swapMaxAmountIn acceptable slippage for swapping /// @param _receiver address to receive unwound collateral function unwind( uint128 _accountId, @@ -360,9 +360,9 @@ contract Zap is Reentrancy, Errors { uint256 _collateralAmount, address _collateral, bytes memory _path, - uint256 _zapTolerance, - uint256 _unwrapTolerance, - uint256 _swapTolerance, + uint256 _zapMinAmountOut, + uint256 _unwrapMinAmountOut, + uint256 _swapMaxAmountIn, address _receiver ) external @@ -377,9 +377,9 @@ contract Zap is Reentrancy, Errors { _collateralAmount, _collateral, _path, - _zapTolerance, - _unwrapTolerance, - _swapTolerance, + _zapMinAmountOut, + _unwrapMinAmountOut, + _swapMaxAmountIn, _receiver ); @@ -461,8 +461,8 @@ contract Zap is Reentrancy, Errors { uint256 _collateralAmount, , , - uint256 _zapTolerance, - uint256 _unwrapTolerance, + uint256 _zapMinAmountOut, + uint256 _unwrapMinAmountOut, , ) = abi.decode( _params, @@ -481,7 +481,7 @@ contract Zap is Reentrancy, Errors { // zap USDC from flashloan into USDx; // ALL USDC flashloaned from Aave is zapped into USDx - uint256 usdxAmount = _zapIn(_flashloan, _zapTolerance); + uint256 usdxAmount = _zapIn(_flashloan, _zapMinAmountOut); // burn USDx to pay off synthetix perp position debt; // debt is denominated in USD and thus repaid with USDx @@ -497,15 +497,23 @@ contract Zap is Reentrancy, Errors { // unwrap withdrawn synthetix perp position collateral; // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) unwound = - _unwrap(_collateralId, _collateralAmount, _unwrapTolerance); + _unwrap(_collateralId, _collateralAmount, _unwrapMinAmountOut); // establish total debt now owed to Aave; // i.e., # of USDC _flashloan += _premium; } - (,,, address _collateral, bytes memory _path,,, uint256 _swapTolerance,) - = abi.decode( + ( + , + , + , + address _collateral, + bytes memory _path, + , + , + uint256 _swapMaxAmountIn, + ) = abi.decode( _params, ( uint128, @@ -527,7 +535,7 @@ contract Zap is Reentrancy, Errors { // whatever collateral amount is remaining is returned to the caller unwound -= _collateral == USDC ? _flashloan - : _swapFor(_collateral, _path, _flashloan, _swapTolerance); + : _swapFor(_collateral, _path, _flashloan, _swapMaxAmountIn); } /// @notice approximate USDC needed to unwind synthetix perp position @@ -689,25 +697,25 @@ contract Zap is Reentrancy, Errors { /// @param _from address of token to swap /// @param _path uniswap swap path encoded in reverse order /// @param _amount amount of USDC to receive in return - /// @param _tolerance or tolerable amount of token to spend + /// @param _maxAmountIn max amount of token to spend /// @param _receiver address to receive USDC /// @return deducted amount of incoming token; i.e., amount spent function swapFor( address _from, bytes memory _path, uint256 _amount, - uint256 _tolerance, + uint256 _maxAmountIn, address _receiver ) external returns (uint256 deducted) { - _pull(_from, msg.sender, _tolerance); - deducted = _swapFor(_from, _path, _amount, _tolerance); + _pull(_from, msg.sender, _maxAmountIn); + deducted = _swapFor(_from, _path, _amount, _maxAmountIn); _push(USDC, _receiver, _amount); - if (deducted < _tolerance) { - _push(_from, msg.sender, _tolerance - deducted); + if (deducted < _maxAmountIn) { + _push(_from, msg.sender, _maxAmountIn - deducted); } } @@ -717,18 +725,18 @@ contract Zap is Reentrancy, Errors { address _from, bytes memory _path, uint256 _amount, - uint256 _tolerance + uint256 _maxAmountIn ) internal returns (uint256 deducted) { - IERC20(_from).approve(ROUTER, _tolerance); + IERC20(_from).approve(ROUTER, _maxAmountIn); IRouter.ExactOutputParams memory params = IRouter.ExactOutputParams({ path: _path, recipient: address(this), amountOut: _amount, - amountInMaximum: _tolerance + amountInMaximum: _maxAmountIn }); try IRouter(ROUTER).exactOutput(params) returns (uint256 amountIn) { @@ -746,7 +754,8 @@ contract Zap is Reentrancy, Errors { /// @param _from address of token to swap /// @param _path uniswap swap path encoded in order /// @param _amount of token to swap - /// @param _tolerance tolerable amount of USDC to receive specified with 6 + /// @param _amountOutMinimum tolerable amount of USDC to receive specified + /// with 6 /// decimals /// @param _receiver address to receive USDC /// @return received amount of USDC @@ -754,14 +763,14 @@ contract Zap is Reentrancy, Errors { address _from, bytes memory _path, uint256 _amount, - uint256 _tolerance, + uint256 _amountOutMinimum, address _receiver ) external returns (uint256 received) { _pull(_from, msg.sender, _amount); - received = _swapWith(_from, _path, _amount, _tolerance); + received = _swapWith(_from, _path, _amount, _amountOutMinimum); _push(USDC, _receiver, received); } @@ -771,7 +780,7 @@ contract Zap is Reentrancy, Errors { address _from, bytes memory _path, uint256 _amount, - uint256 _tolerance + uint256 _amountOutMinimum ) internal returns (uint256 received) @@ -782,7 +791,7 @@ contract Zap is Reentrancy, Errors { path: _path, recipient: address(this), amountIn: _amount, - amountOutMinimum: _tolerance + amountOutMinimum: _amountOutMinimum }); try IRouter(ROUTER).exactInput(params) returns (uint256 amountOut) { diff --git a/test/Buy.t.sol b/test/Buy.t.sol index 190d4e7..7e21ad9 100644 --- a/test/Buy.t.sol +++ b/test/Buy.t.sol @@ -22,14 +22,14 @@ contract BuyTest is Bootstrap { (uint256 received, address synth) = zap.buy({ _synthId: zap.SUSDC_SPOT_ID(), _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); assertEq(synth, address(susdc)); - assertGe(received, DEFAULT_TOLERANCE); + assertGe(received, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdx.balanceOf(ACTOR), 0); - assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_MIN_AMOUNT_OUT); } function test_buy_arbitrum(uint32 amount) public arbitrum { @@ -40,14 +40,14 @@ contract BuyTest is Bootstrap { (uint256 received, address synth) = zap.buy({ _synthId: zap.SUSDC_SPOT_ID(), _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); assertEq(synth, address(susdc)); - assertGe(received, DEFAULT_TOLERANCE); + assertGe(received, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdx.balanceOf(ACTOR), 0); - assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_MIN_AMOUNT_OUT); } } diff --git a/test/Sell.t.sol b/test/Sell.t.sol index b4476dd..cbbfb78 100644 --- a/test/Sell.t.sol +++ b/test/Sell.t.sol @@ -20,21 +20,21 @@ contract SellTest is Bootstrap { (uint256 received,) = zap.buy({ _synthId: zap.SUSDC_SPOT_ID(), _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); assertEq(usdx.balanceOf(ACTOR), 0); - assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_MIN_AMOUNT_OUT); susdc.approve(address(zap), type(uint256).max); received = zap.sell({ _synthId: zap.SUSDC_SPOT_ID(), _amount: received, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(received, DEFAULT_TOLERANCE); - assertGe(usdx.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertGe(received, DEFAULT_MIN_AMOUNT_OUT); + assertGe(usdx.balanceOf(ACTOR), DEFAULT_MIN_AMOUNT_OUT); assertEq(susdc.balanceOf(ACTOR), 0); } @@ -44,21 +44,21 @@ contract SellTest is Bootstrap { (uint256 received,) = zap.buy({ _synthId: zap.SUSDC_SPOT_ID(), _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); assertEq(usdx.balanceOf(ACTOR), 0); - assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_MIN_AMOUNT_OUT); susdc.approve(address(zap), type(uint256).max); received = zap.sell({ _synthId: zap.SUSDC_SPOT_ID(), _amount: received, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(received, DEFAULT_TOLERANCE); - assertGe(usdx.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertGe(received, DEFAULT_MIN_AMOUNT_OUT); + assertGe(usdx.balanceOf(ACTOR), DEFAULT_MIN_AMOUNT_OUT); assertEq(susdc.balanceOf(ACTOR), 0); } diff --git a/test/Swap.for.t.sol b/test/Swap.for.t.sol index 73d63ed..8892a86 100644 --- a/test/Swap.for.t.sol +++ b/test/Swap.for.t.sol @@ -19,20 +19,20 @@ contract SwapForTest is Bootstrap { function test_swap_for_single_base() public base { uint256 amount = 100e6; - uint256 tolerance = type(uint256).max / 4; - _spin(ACTOR, weth, tolerance, address(zap)); + uint256 _maxAmountIn = type(uint256).max / 4; + _spin(ACTOR, weth, _maxAmountIn, address(zap)); assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.balanceOf(ACTOR), _maxAmountIn); vm.startPrank(ACTOR); zap.swapFor({ _from: address(weth), _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), _amount: amount, - _tolerance: tolerance, + _maxAmountIn: _maxAmountIn, _receiver: ACTOR }); assertGt(usdc.balanceOf(ACTOR), 0); - assertLt(weth.balanceOf(ACTOR), tolerance); + assertLt(weth.balanceOf(ACTOR), _maxAmountIn); assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); vm.stopPrank(); } @@ -40,8 +40,8 @@ contract SwapForTest is Bootstrap { function test_swap_for_single_arbitrum(uint8 percentage) public arbitrum { vm.assume(percentage < 95 && percentage > 0); - uint256 tolerance = type(uint256).max; - _spin(ACTOR, weth, tolerance, address(zap)); + uint256 _maxAmountIn = type(uint256).max; + _spin(ACTOR, weth, _maxAmountIn, address(zap)); address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( address(weth), address(usdc), zap.FEE_TIER() @@ -50,7 +50,7 @@ contract SwapForTest is Bootstrap { uint256 amount = depth * (percentage / 100); assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.balanceOf(ACTOR), _maxAmountIn); vm.startPrank(ACTOR); @@ -60,9 +60,9 @@ contract SwapForTest is Bootstrap { zap.swapFor({ _from: address(weth), - _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), + _path: abi.encodePacked(address(usdc), FEE_5, address(weth)), _amount: amount, - _tolerance: tolerance, + _maxAmountIn: _maxAmountIn, _receiver: ACTOR }); @@ -71,7 +71,7 @@ contract SwapForTest is Bootstrap { ? usdc.balanceOf(ACTOR) < 0 : usdc.balanceOf(ACTOR) == 0 ); - assertLe(weth.balanceOf(ACTOR), tolerance); + assertLe(weth.balanceOf(ACTOR), _maxAmountIn); assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); vm.stopPrank(); @@ -82,64 +82,46 @@ contract SwapForTest is Bootstrap { function test_swap_for_multihop_base() public base { uint256 amount = 100e6; - uint256 tolerance = type(uint256).max / 4; - _spin(ACTOR, weth, tolerance, address(zap)); + uint256 _maxAmountIn = type(uint256).max / 4; + _spin(ACTOR, tbtc, _maxAmountIn, address(zap)); assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), tolerance); + assertEq(tbtc.balanceOf(ACTOR), _maxAmountIn); vm.startPrank(ACTOR); zap.swapFor({ - _from: address(weth), - _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), + _from: address(tbtc), + _path: abi.encodePacked( + address(usdc), FEE_30, address(weth), FEE_30, address(tbtc) + ), _amount: amount, - _tolerance: tolerance, + _maxAmountIn: _maxAmountIn, _receiver: ACTOR }); assertGt(usdc.balanceOf(ACTOR), 0); - assertLt(weth.balanceOf(ACTOR), tolerance); - assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); + assertLt(tbtc.balanceOf(ACTOR), _maxAmountIn); + assertEq(tbtc.allowance(address(zap), zap.ROUTER()), 0); vm.stopPrank(); } - function test_swap_for_multihop_arbitrum(uint8 percentage) - public - arbitrum - { - vm.assume(percentage < 95 && percentage > 0); - - uint256 tolerance = type(uint256).max; - _spin(ACTOR, weth, tolerance, address(zap)); - - address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( - address(weth), address(usdc), zap.FEE_TIER() - ); - uint256 depth = usdc.balanceOf(pool); - uint256 amount = depth * (percentage / 100); - + function test_swap_for_multihop_arbitrum() public arbitrum { + uint256 amount = 100e6; + uint256 _maxAmountIn = type(uint256).max / 4; + _spin(ACTOR, tbtc, _maxAmountIn, address(zap)); assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), tolerance); + assertEq(tbtc.balanceOf(ACTOR), _maxAmountIn); vm.startPrank(ACTOR); - - if (amount == 0) { - vm.expectRevert(); - } - zap.swapFor({ - _from: address(weth), - _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), + _from: address(tbtc), + _path: abi.encodePacked( + address(usdc), FEE_5, address(weth), FEE_5, address(tbtc) + ), _amount: amount, - _tolerance: tolerance, + _maxAmountIn: _maxAmountIn, _receiver: ACTOR }); - - assertTrue( - amount > 1e6 - ? usdc.balanceOf(ACTOR) < 0 - : usdc.balanceOf(ACTOR) == 0 - ); - assertLe(weth.balanceOf(ACTOR), tolerance); - assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); - + assertGt(usdc.balanceOf(ACTOR), 0); + assertLt(tbtc.balanceOf(ACTOR), _maxAmountIn); + assertEq(tbtc.allowance(address(zap), zap.ROUTER()), 0); vm.stopPrank(); } diff --git a/test/Swap.with.t.sol b/test/Swap.with.t.sol index d552f79..37fae04 100644 --- a/test/Swap.with.t.sol +++ b/test/Swap.with.t.sol @@ -20,8 +20,8 @@ contract SwapWithTest is Bootstrap { function test_swap_with_base(uint8 percentage) public base { vm.assume(percentage < 95 && percentage > 0); - uint256 tolerance = type(uint256).max; - _spin(ACTOR, weth, tolerance, address(zap)); + uint256 amountOutMinimum = type(uint256).max; + _spin(ACTOR, weth, amountOutMinimum, address(zap)); address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( address(weth), address(usdc), zap.FEE_TIER() @@ -30,7 +30,7 @@ contract SwapWithTest is Bootstrap { uint256 amount = depth * (percentage / 100); assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.balanceOf(ACTOR), amountOutMinimum); vm.startPrank(ACTOR); @@ -42,7 +42,7 @@ contract SwapWithTest is Bootstrap { _from: address(weth), _path: abi.encodePacked(address(weth), FEE_30, address(usdx)), _amount: amount, - _tolerance: tolerance, + _amountOutMinimum: amountOutMinimum, _receiver: ACTOR }); @@ -51,7 +51,7 @@ contract SwapWithTest is Bootstrap { ? usdc.balanceOf(ACTOR) < 0 : usdc.balanceOf(ACTOR) == 0 ); - assertLe(weth.balanceOf(ACTOR), tolerance); + assertLe(weth.balanceOf(ACTOR), amountOutMinimum); assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); vm.stopPrank(); @@ -61,8 +61,8 @@ contract SwapWithTest is Bootstrap { function test_swap_with_arbitrum(uint8 percentage) public arbitrum { vm.assume(percentage < 95 && percentage > 0); - uint256 tolerance = type(uint256).max; - _spin(ACTOR, weth, tolerance, address(zap)); + uint256 amountOutMinimum = type(uint256).max; + _spin(ACTOR, weth, amountOutMinimum, address(zap)); address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( address(weth), address(usdc), zap.FEE_TIER() @@ -71,7 +71,7 @@ contract SwapWithTest is Bootstrap { uint256 amount = depth * (percentage / 100); assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), tolerance); + assertEq(weth.balanceOf(ACTOR), amountOutMinimum); vm.startPrank(ACTOR); @@ -83,7 +83,7 @@ contract SwapWithTest is Bootstrap { _from: address(weth), _path: abi.encodePacked(address(weth), FEE_30, address(usdc)), _amount: amount, - _tolerance: tolerance, + _amountOutMinimum: amountOutMinimum, _receiver: ACTOR }); @@ -92,7 +92,7 @@ contract SwapWithTest is Bootstrap { ? usdc.balanceOf(ACTOR) < 0 : usdc.balanceOf(ACTOR) == 0 ); - assertLe(weth.balanceOf(ACTOR), tolerance); + assertLe(weth.balanceOf(ACTOR), amountOutMinimum); assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); vm.stopPrank(); diff --git a/test/Unwrap.t.sol b/test/Unwrap.t.sol index b900b7c..12bc838 100644 --- a/test/Unwrap.t.sol +++ b/test/Unwrap.t.sol @@ -21,21 +21,21 @@ contract UnwrapTest is Bootstrap { _token: address(usdc), _synthId: zap.SUSDC_SPOT_ID(), _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); assertEq(usdc.balanceOf(ACTOR), 0); - assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_MIN_AMOUNT_OUT); susdc.approve(address(zap), type(uint256).max); uint256 unwrapped = zap.unwrap({ _token: address(usdc), _synthId: zap.SUSDC_SPOT_ID(), _amount: wrapped, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(unwrapped, DEFAULT_TOLERANCE); + assertGe(unwrapped, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdc.balanceOf(ACTOR), amount); assertEq(susdc.balanceOf(ACTOR), 0); } @@ -47,21 +47,21 @@ contract UnwrapTest is Bootstrap { _token: address(usdc), _synthId: zap.SUSDC_SPOT_ID(), _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); assertEq(usdc.balanceOf(ACTOR), 0); - assertGe(susdc.balanceOf(ACTOR), DEFAULT_TOLERANCE); + assertGe(susdc.balanceOf(ACTOR), DEFAULT_MIN_AMOUNT_OUT); susdc.approve(address(zap), type(uint256).max); uint256 unwrapped = zap.unwrap({ _token: address(usdc), _synthId: zap.SUSDC_SPOT_ID(), _amount: wrapped, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(unwrapped, DEFAULT_TOLERANCE); + assertGe(unwrapped, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdc.balanceOf(ACTOR), amount); assertEq(susdc.balanceOf(ACTOR), 0); } diff --git a/test/Wrap.t.sol b/test/Wrap.t.sol index ba7d088..26eb2be 100644 --- a/test/Wrap.t.sol +++ b/test/Wrap.t.sol @@ -23,11 +23,11 @@ contract WrapTest is Bootstrap { _token: address(usdc), _synthId: zap.SUSDC_SPOT_ID(), _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(wrapped, DEFAULT_TOLERANCE); + assertGe(wrapped, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdc.balanceOf(ACTOR), 0); assertEq(susdc.balanceOf(ACTOR), wrapped); } @@ -41,11 +41,11 @@ contract WrapTest is Bootstrap { _token: address(usdc), _synthId: zap.SUSDC_SPOT_ID(), _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(wrapped, DEFAULT_TOLERANCE); + assertGe(wrapped, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdc.balanceOf(ACTOR), 0); assertEq(susdc.balanceOf(ACTOR), wrapped); } diff --git a/test/Zap.in.t.sol b/test/Zap.in.t.sol index 4596acb..5807fa5 100644 --- a/test/Zap.in.t.sol +++ b/test/Zap.in.t.sol @@ -20,11 +20,11 @@ contract ZapInTest is Bootstrap { vm.startPrank(ACTOR); uint256 zapped = zap.zapIn({ _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(zapped, DEFAULT_TOLERANCE); + assertGe(zapped, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdc.balanceOf(ACTOR), 0); assertEq(usdx.balanceOf(ACTOR), zapped); } @@ -36,11 +36,11 @@ contract ZapInTest is Bootstrap { vm.startPrank(ACTOR); uint256 zapped = zap.zapIn({ _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(zapped, DEFAULT_TOLERANCE); + assertGe(zapped, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdc.balanceOf(ACTOR), 0); assertEq(usdx.balanceOf(ACTOR), zapped); } diff --git a/test/Zap.out.t.sol b/test/Zap.out.t.sol index 3f1dd1c..e2b3ab1 100644 --- a/test/Zap.out.t.sol +++ b/test/Zap.out.t.sol @@ -21,11 +21,11 @@ contract ZapOutTest is Bootstrap { vm.startPrank(ACTOR); uint256 zapped = zap.zapOut({ _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(zapped, DEFAULT_TOLERANCE); + assertGe(zapped, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdc.balanceOf(ACTOR), zapped); assertEq(usdx.balanceOf(ACTOR), 0); } @@ -38,11 +38,11 @@ contract ZapOutTest is Bootstrap { vm.startPrank(ACTOR); uint256 zapped = zap.zapOut({ _amount: amount, - _tolerance: DEFAULT_TOLERANCE, + _minAmountOut: DEFAULT_MIN_AMOUNT_OUT, _receiver: ACTOR }); vm.stopPrank(); - assertGe(zapped, DEFAULT_TOLERANCE); + assertGe(zapped, DEFAULT_MIN_AMOUNT_OUT); assertEq(usdc.balanceOf(ACTOR), zapped); assertEq(usdx.balanceOf(ACTOR), 0); } diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index b9a050d..be5c6fa 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -13,7 +13,7 @@ contract Constants { /// @custom:values address constant ACTOR = 0x7777777777777777777777777777777777777777; - uint256 constant DEFAULT_TOLERANCE = 0; + uint256 constant DEFAULT_MIN_AMOUNT_OUT = 0; /// @custom:tokens address constant ARBITRUM_WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; @@ -22,6 +22,8 @@ contract Constants { address constant BASE_WETH = 0x4200000000000000000000000000000000000006; address constant BASE_TBTC = 0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b; + uint24 constant FEE_1 = 100; + uint24 constant FEE_5 = 500; uint24 constant FEE_30 = 3000; uint24 constant FEE_100 = 10_000; From a7e11d436b1bdfdbc5f1c352708d699d8750a488 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:14:08 -0400 Subject: [PATCH 112/129] =?UTF-8?q?=F0=9F=91=B7=E2=80=8D=E2=99=82=EF=B8=8F?= =?UTF-8?q?=20add=20excess=20logic=20to=20burn()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 15e78a5..159c443 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -573,18 +573,23 @@ contract Zap is Reentrancy, Errors { /// @dev excess USDx will be returned to the caller /// @param _amount amount of USDx to burn /// @param _accountId synthetix perp market account id - /// @return remaining amount of USDx returned to the caller + /// @return excess amount of USDx returned to the caller function burn( uint256 _amount, uint128 _accountId ) external - returns (uint256 remaining) + returns (uint256 excess) { + excess = IERC20(USDX).balanceOf(address(this)); + + // pull and burn _pull(USDX, msg.sender, _amount); _burn(_amount, _accountId); - remaining = IERC20(USDX).balanceOf(address(this)); - if (remaining > 0) _push(USDX, msg.sender, remaining); + + excess = IERC20(USDX).balanceOf(address(this)) - excess; + + if (excess > 0) _push(USDX, msg.sender, excess); } /// @dev allowance is assumed From ce607d8010279eb90c4e797e05c5d330e2142e76 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:17:08 -0400 Subject: [PATCH 113/129] =?UTF-8?q?=F0=9F=93=B8=20update=20lcov/gas-snapsh?= =?UTF-8?q?ot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 68 ++++---- lcov.info | 450 +++++++++++++++++++++++++++----------------------- 2 files changed, 275 insertions(+), 243 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index c1d4674..6950c02 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,33 +1,35 @@ -AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 256, μ: 3154608, ~: 3154607) -BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 256, μ: 3130817, ~: 3130817) -BuyTest:test_buy_arbitrum(uint32) (runs: 256, μ: 3666974, ~: 3666974) -BuyTest:test_buy_base(uint32) (runs: 256, μ: 3586613, ~: 3586613) -FlushTest:test_flush(uint32) (runs: 256, μ: 3130839, ~: 3130839) -ReentrancyTest:test_requireStage(uint8) (runs: 256, μ: 15037, ~: 5846) -ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 15266, ~: 7408) -ReentrancyTest:test_stage_default() (gas: 2392) -ReentrancyTest:test_stage_enum_level1() (gas: 247) -ReentrancyTest:test_stage_enum_level2() (gas: 224) -ReentrancyTest:test_stage_enum_unset() (gas: 235) -SellTest:test_sell_arbitrum(uint32) (runs: 256, μ: 3829418, ~: 3829418) -SellTest:test_sell_base(uint32) (runs: 256, μ: 3730858, ~: 3730858) -SwapForTest:test_swap_for_arbitrum(uint8) (runs: 256, μ: 3378814, ~: 3378814) -SwapForTest:test_swap_for_arbitrum_sepolia() (gas: 3130749) -SwapForTest:test_swap_for_base() (gas: 3418014) -SwapWithTest:test_swap_with_arbitrum() (gas: 3150676) -SwapWithTest:test_swap_with_arbitrum_sepolia() (gas: 3130727) -SwapWithTest:test_swap_with_base() (gas: 3150677) -UnwindTest:test_unwind_arbitrum_sepolia() (gas: 3130704) -UnwindTest:test_unwind_is_authorized() (gas: 3171859) -UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 256, μ: 3862015, ~: 3862015) -UnwrapTest:test_unwrap_base(uint32) (runs: 256, μ: 3753951, ~: 3753951) -WithdrawTest:test_withdraw_arbitrum() (gas: 3951963) -WithdrawTest:test_withdraw_base() (gas: 3775357) -WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 3171611) -WithdrawTest:test_withdraw_is_authorized_base() (gas: 3171621) -WrapTest:test_wrap_arbitrum(uint32) (runs: 256, μ: 3690933, ~: 3690933) -WrapTest:test_wrap_base(uint32) (runs: 256, μ: 3600514, ~: 3600514) -ZapInTest:test_zap_in_arbitrum(uint32) (runs: 256, μ: 3823038, ~: 3823038) -ZapInTest:test_zap_in_base(uint32) (runs: 256, μ: 3720000, ~: 3720000) -ZapOutTest:test_zap_out_arbitum(uint32) (runs: 256, μ: 3781549, ~: 3781549) -ZapOutTest:test_zap_out_base(uint32) (runs: 256, μ: 3683431, ~: 3683431) \ No newline at end of file +AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 256, μ: 3908016, ~: 3908015) +BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 256, μ: 3859004, ~: 3859004) +BuyTest:test_buy_arbitrum(uint32) (runs: 256, μ: 4467739, ~: 4467739) +BuyTest:test_buy_base(uint32) (runs: 256, μ: 4387367, ~: 4387367) +FlushTest:test_flush(uint32) (runs: 256, μ: 3859026, ~: 3859026) +ReentrancyTest:test_requireStage(uint8) (runs: 256, μ: 15532, ~: 6343) +ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 16427, ~: 8897) +ReentrancyTest:test_stage_default() (gas: 2435) +ReentrancyTest:test_stage_enum_level1() (gas: 250) +ReentrancyTest:test_stage_enum_level2() (gas: 294) +ReentrancyTest:test_stage_enum_unset() (gas: 293) +SellTest:test_sell_arbitrum(uint32) (runs: 256, μ: 4635354, ~: 4635354) +SellTest:test_sell_base(uint32) (runs: 256, μ: 4536794, ~: 4536794) +SwapForTest:test_swap_for_multihop_arbitrum() (gas: 4346260) +SwapForTest:test_swap_for_multihop_base() (gas: 4348510) +SwapForTest:test_swap_for_single_arbitrum(uint8) (runs: 256, μ: 4194700, ~: 4194700) +SwapForTest:test_swap_for_single_arbitrum_sepolia() (gas: 3858759) +SwapForTest:test_swap_for_single_base() (gas: 4208679) +SwapWithTest:test_swap_with_arbitrum(uint8) (runs: 256, μ: 4168946, ~: 4168946) +SwapWithTest:test_swap_with_arbitrum_sepolia() (gas: 3858803) +SwapWithTest:test_swap_with_base(uint8) (runs: 256, μ: 4102090, ~: 4102090) +UnwindTest:test_unwind_arbitrum_sepolia() (gas: 3858736) +UnwindTest:test_unwind_is_authorized() (gas: 3926493) +UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 256, μ: 4679724, ~: 4679724) +UnwrapTest:test_unwrap_base(uint32) (runs: 256, μ: 4571660, ~: 4571660) +WithdrawTest:test_withdraw_arbitrum() (gas: 4751268) +WithdrawTest:test_withdraw_base() (gas: 4574617) +WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 3924093) +WithdrawTest:test_withdraw_is_authorized_base() (gas: 3924092) +WrapTest:test_wrap_arbitrum(uint32) (runs: 256, μ: 4503212, ~: 4503212) +WrapTest:test_wrap_base(uint32) (runs: 256, μ: 4412793, ~: 4412793) +ZapInTest:test_zap_in_arbitrum(uint32) (runs: 256, μ: 4636324, ~: 4636324) +ZapInTest:test_zap_in_base(uint32) (runs: 256, μ: 4533297, ~: 4533297) +ZapOutTest:test_zap_out_arbitum(uint64) (runs: 256, μ: 4609235, ~: 4609235) +ZapOutTest:test_zap_out_base(uint64) (runs: 256, μ: 4511128, ~: 4511128) \ No newline at end of file diff --git a/lcov.info b/lcov.info index a74eab1..73f9cb2 100644 --- a/lcov.info +++ b/lcov.info @@ -7,7 +7,7 @@ DA:15,0 DA:17,0 FN:20,Deploy.deploySystem FNDA:0,Deploy.deploySystem -DA:34,4107 +DA:34,4619 FN:55,DeployBase.run FNDA:0,DeployBase.run DA:56,0 @@ -26,95 +26,91 @@ BRH:0 end_of_record TN: SF:src/Zap.sol -FN:45,Zap. -FNDA:4107,Zap. -DA:57,4107 -DA:60,4107 -DA:61,4107 -DA:62,4107 -DA:63,4107 -DA:64,4107 -DA:67,4107 -DA:70,4107 -DA:71,4107 -FN:80,Zap.isAuthorized -FNDA:4,Zap.isAuthorized -DA:81,4 -DA:84,4 -BRDA:84,0,0,1 -BRDA:84,0,1,- -FN:89,Zap.onlyAave +FN:46,Zap. +FNDA:4619,Zap. +DA:58,4619 +DA:61,4619 +DA:62,4619 +DA:63,4619 +DA:64,4619 +DA:65,4619 +DA:68,4619 +DA:71,4619 +DA:72,4619 +FN:81,Zap.isAuthorized +FNDA:1,Zap.isAuthorized +DA:82,1 +DA:85,1 +BRDA:85,0,0,2 +BRDA:85,0,1,2 +FN:90,Zap.onlyAave FNDA:256,Zap.onlyAave -DA:90,256 -BRDA:90,1,0,256 -BRDA:90,1,1,- -FN:104,Zap.zapIn +DA:91,256 +BRDA:91,1,0,256 +BRDA:91,1,1,- +FN:105,Zap.zapIn FNDA:512,Zap.zapIn -DA:112,512 DA:113,512 DA:114,512 -FN:119,Zap._zapIn +DA:115,512 +FN:120,Zap._zapIn FNDA:512,Zap._zapIn -DA:126,512 DA:127,512 -FN:136,Zap.zapOut +DA:128,512 +FN:137,Zap.zapOut FNDA:512,Zap.zapOut -DA:144,512 DA:145,512 DA:146,512 -FN:151,Zap._zapOut +DA:147,512 +FN:152,Zap._zapOut FNDA:512,Zap._zapOut -DA:158,512 DA:159,512 -FN:175,Zap.wrap +DA:160,512 +FN:176,Zap.wrap FNDA:1024,Zap.wrap -DA:185,1024 DA:186,1024 DA:187,1024 -FN:192,Zap._wrap +DA:188,1024 +FN:193,Zap._wrap FNDA:1536,Zap._wrap -DA:201,1536 DA:202,1536 -FN:218,Zap.unwrap +DA:203,1536 +FN:219,Zap.unwrap FNDA:512,Zap.unwrap -DA:228,512 DA:229,512 DA:230,512 DA:231,512 -FN:236,Zap._unwrap +DA:232,512 +FN:237,Zap._unwrap FNDA:1024,Zap._unwrap -DA:244,1024 DA:245,1024 DA:246,1024 -FN:264,Zap.buy +DA:247,1024 +FN:265,Zap.buy FNDA:1024,Zap.buy -DA:273,1024 DA:274,1024 DA:275,1024 DA:276,1024 -FN:281,Zap._buy +DA:277,1024 +FN:282,Zap._buy FNDA:1536,Zap._buy -DA:289,1536 DA:290,1536 -DA:295,1536 -DA:296,1536 -DA:297,0 -DA:298,0 -FN:309,Zap.sell +DA:291,1536 +FN:306,Zap.sell FNDA:512,Zap.sell +DA:315,512 +DA:316,512 +DA:317,512 DA:318,512 -DA:319,512 -DA:320,512 -DA:321,512 -FN:326,Zap._sell +FN:323,Zap._sell FNDA:1024,Zap._sell -DA:334,1024 -DA:335,1024 -DA:336,1024 -FN:359,Zap.unwind +DA:331,1024 +DA:332,1024 +DA:333,1024 +FN:357,Zap.unwind FNDA:1,Zap.unwind -DA:373,0 -DA:375,0 +DA:372,0 +DA:374,0 DA:387,0 DA:389,0 DA:397,0 @@ -122,134 +118,135 @@ FN:407,Zap.executeOperation FNDA:256,Zap.executeOperation DA:419,0 DA:421,0 -DA:435,0 -DA:437,0 -DA:439,0 -FN:447,Zap._unwind +DA:436,0 +DA:438,0 +DA:440,0 +FN:448,Zap._unwind FNDA:0,Zap._unwind -DA:456,0 -DA:465,0 -DA:481,0 -DA:485,0 -DA:493,0 -DA:497,0 -DA:501,0 -DA:508,0 -FN:516,Zap._approximateLoanNeeded +DA:458,0 +DA:467,0 +DA:484,0 +DA:488,0 +DA:495,0 +DA:499,0 +DA:504,0 +DA:507,0 +DA:516,0 +DA:536,0 +FN:544,Zap._approximateLoanNeeded FNDA:0,Zap._approximateLoanNeeded -DA:522,0 -DA:524,0 -DA:525,0 -DA:530,0 -DA:535,0 -FN:548,Zap.burn -FNDA:0,Zap.burn -DA:549,0 DA:550,0 -DA:551,0 DA:552,0 -BRDA:552,2,0,- -FN:557,Zap._burn -FNDA:0,Zap._burn +DA:553,0 DA:558,0 -DA:559,0 -DA:560,0 -FN:573,Zap.withdraw +DA:563,0 +FN:577,Zap.burn +FNDA:0,Zap.burn +DA:584,0 +DA:587,0 +DA:588,0 +DA:590,0 +DA:592,0 +BRDA:592,2,0,- +FN:597,Zap._burn +FNDA:0,Zap._burn +DA:598,0 +DA:599,0 +DA:600,0 +FN:613,Zap.withdraw FNDA:4,Zap.withdraw -DA:582,2 -DA:583,2 -DA:586,2 -FN:592,Zap._withdraw +DA:622,2 +DA:623,2 +DA:626,2 +FN:632,Zap._withdraw FNDA:2,Zap._withdraw -DA:599,2 -DA:600,2 -DA:605,2 -FN:624,Zap.quoteSwapFor +DA:639,2 +DA:640,2 +DA:645,2 +FN:660,Zap.quoteSwapFor FNDA:0,Zap.quoteSwapFor -DA:639,0 -FN:658,Zap.quoteSwapWith +DA:672,0 +FN:683,Zap.quoteSwapWith FNDA:0,Zap.quoteSwapWith -DA:673,0 -FN:688,Zap.swapFor -FNDA:257,Zap.swapFor -DA:697,257 -DA:698,257 -DA:699,1 -DA:701,1 -BRDA:701,3,0,1 -DA:702,1 -FN:708,Zap._swapFor -FNDA:257,Zap._swapFor -DA:716,257 -DA:718,257 -DA:729,257 -DA:730,1 -DA:731,1 -DA:732,256 -DA:733,256 -DA:736,1 -FN:747,Zap.swapWith -FNDA:0,Zap.swapWith -DA:756,0 -DA:757,0 -DA:758,0 -FN:763,Zap._swapWith +DA:695,0 +FN:708,Zap.swapFor +FNDA:259,Zap.swapFor +DA:718,259 +DA:719,259 +DA:720,3 +DA:722,3 +BRDA:722,3,0,3 +DA:723,3 +FN:729,Zap._swapFor +FNDA:259,Zap._swapFor +DA:738,259 +DA:740,259 +DA:747,259 +DA:748,3 +DA:749,256 +DA:750,256 +DA:753,3 +FN:767,Zap.swapWith +FNDA:512,Zap.swapWith +DA:777,512 +DA:778,0 +DA:779,0 +FN:784,Zap._swapWith FNDA:0,Zap._swapWith -DA:771,0 -DA:773,0 -DA:784,0 -DA:785,0 -DA:786,0 -DA:787,0 -DA:788,0 -FN:801,Zap._pull -FNDA:4353,Zap._pull -DA:809,4353 -DA:811,4353 -DA:813,4353 -DA:814,4353 -DA:815,0 -DA:816,0 -FN:825,Zap._push -FNDA:4100,Zap._push -DA:833,4100 -BRDA:833,4,0,- -BRDA:833,4,1,4100 -DA:834,4100 -DA:836,4100 -DA:837,4100 -DA:838,0 -DA:839,0 +DA:793,0 +DA:795,0 +DA:802,0 +DA:803,0 +DA:804,0 +DA:805,0 +FN:817,Zap._pull +FNDA:4867,Zap._pull +DA:818,4867 +BRDA:818,4,0,512 +BRDA:818,4,1,4355 +DA:819,4355 +DA:821,4355 +FN:828,Zap._push +FNDA:4104,Zap._push +DA:835,4104 +BRDA:835,5,0,- +BRDA:835,5,1,4104 +DA:836,4104 +BRDA:836,6,0,- +BRDA:836,6,1,4104 +DA:837,4104 +DA:839,4104 FNF:31 -FNH:23 -LF:124 -LH:76 -BRF:8 -BRH:4 +FNH:24 +LF:116 +LH:73 +BRF:12 +BRH:8 end_of_record TN: SF:src/utils/Flush.sol -FN:16,Flush. +FN:19,Flush. FNDA:0,Flush. -DA:17,0 -FN:23,Flush.flush +DA:20,0 +FN:26,Flush.flush FNDA:0,Flush.flush -DA:24,0 -BRDA:24,0,0,- -BRDA:24,0,1,- -DA:25,0 -DA:26,0 DA:27,0 -BRDA:27,1,0,- -FN:34,Flush.designatePlumber +BRDA:27,0,0,- +BRDA:27,0,1,- +DA:28,0 +DA:29,0 +DA:30,0 +BRDA:30,1,0,- +FN:37,Flush.designatePlumber FNDA:0,Flush.designatePlumber -DA:35,0 -BRDA:35,2,0,- -BRDA:35,2,1,- -DA:36,0 +DA:38,0 +BRDA:38,2,0,- +BRDA:38,2,1,- +DA:39,0 +DA:40,0 FNF:3 FNH:0 -LF:7 +LF:8 LH:0 BRF:5 BRH:0 @@ -260,9 +257,9 @@ FN:27,Reentrancy.requireStage FNDA:0,Reentrancy.requireStage DA:28,0 FN:32,Reentrancy._requireStage -FNDA:423,Reentrancy._requireStage -DA:33,423 -BRDA:33,0,0,167 +FNDA:424,Reentrancy._requireStage +DA:33,424 +BRDA:33,0,0,168 BRDA:33,0,1,256 FNF:2 FNH:1 @@ -272,56 +269,89 @@ BRF:2 BRH:2 end_of_record TN: +SF:src/utils/SafeTransferERC20.sol +FN:32,SafeERC20.safeTransfer +FNDA:4104,SafeERC20.safeTransfer +DA:33,4104 +FN:42,SafeERC20.safeTransferFrom +FNDA:4355,SafeERC20.safeTransferFrom +DA:50,4355 +FN:67,SafeERC20._callOptionalReturn +FNDA:8459,SafeERC20._callOptionalReturn +DA:68,8459 +DA:69,8459 +DA:71,8459 +DA:74,8459 +BRDA:74,0,0,- +DA:75,0 +DA:76,0 +DA:77,0 +DA:79,8459 +DA:80,8459 +DA:84,8459 +DA:85,0 +BRDA:85,1,0,- +DA:86,0 +FNF:3 +FNH:3 +LF:14 +LH:9 +BRF:2 +BRH:0 +end_of_record +TN: SF:test/utils/Bootstrap.sol -FN:40,Bootstrap.setUp -FNDA:33,Bootstrap.setUp -DA:41,33 -DA:42,33 -DA:43,33 -DA:44,33 -DA:46,33 -DA:47,33 -DA:48,33 -FN:52,Bootstrap.base -FNDA:1539,Bootstrap.base -DA:54,1539 -DA:57,1539 -DA:70,1539 -DA:71,1539 -DA:72,1539 -DA:73,1539 -DA:74,1539 -DA:75,1539 -FN:80,Bootstrap.arbitrum -FNDA:2051,Bootstrap.arbitrum -DA:82,2051 -DA:85,2051 -DA:98,2051 -DA:99,2051 -DA:100,2051 -DA:101,2051 -DA:102,2051 -DA:103,2051 -FN:108,Bootstrap.arbitrum_sepolia +FN:41,Bootstrap.setUp +FNDA:35,Bootstrap.setUp +DA:42,35 +DA:43,35 +DA:44,35 +DA:45,35 +DA:47,35 +DA:48,35 +DA:49,35 +FN:53,Bootstrap.base +FNDA:1794,Bootstrap.base +DA:55,1794 +DA:58,1794 +DA:71,1794 +DA:72,1794 +DA:73,1794 +DA:74,1794 +DA:75,1794 +DA:76,1794 +DA:77,1794 +FN:82,Bootstrap.arbitrum +FNDA:2306,Bootstrap.arbitrum +DA:84,2306 +DA:87,2306 +DA:100,2306 +DA:101,2306 +DA:102,2306 +DA:103,2306 +DA:104,2306 +DA:105,2306 +DA:106,2306 +FN:110,Bootstrap.arbitrum_sepolia FNDA:515,Bootstrap.arbitrum_sepolia -DA:110,515 -DA:113,515 -DA:126,515 -DA:127,515 +DA:112,515 +DA:115,515 DA:128,515 DA:129,515 DA:130,515 DA:131,515 -FN:142,Bootstrap._spin -FNDA:3331,Bootstrap._spin -DA:150,3331 -DA:151,3331 -DA:152,3331 -DA:153,3331 +DA:132,515 +DA:133,515 +FN:144,Bootstrap._spin +FNDA:3845,Bootstrap._spin +DA:152,3845 +DA:153,3845 +DA:154,3845 +DA:155,3845 FNF:5 FNH:5 -LF:35 -LH:35 +LF:37 +LH:37 BRF:0 BRH:0 end_of_record From 53a8d0833d74f3ed604d096f52fb4236b103d315 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:34:12 -0400 Subject: [PATCH 114/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20enable=20via-ir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry.toml b/foundry.toml index 8db7c35..66303e6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,6 +6,7 @@ libs = ['lib'] solc_version = "0.8.27" optimizer = true optimizer_runs = 1_000_000 +via-ir = true [fmt] line_length = 80 From bb3cb1584db5a2649b57ef5395ad06afd11f7c0c Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:34:44 -0400 Subject: [PATCH 115/129] =?UTF-8?q?=F0=9F=91=B7=E2=80=8D=E2=99=82=EF=B8=8F?= =?UTF-8?q?=20add=20flush=20and=20optimize=20=5Funwind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Zap.sol | 95 ++++++++++++++++++++++------------------------------- 1 file changed, 39 insertions(+), 56 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 159c443..538b7fb 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -6,6 +6,8 @@ import {IERC20} from "./interfaces/IERC20.sol"; import {IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; import {IQuoter, IRouter} from "./interfaces/IUniswap.sol"; import {Errors} from "./utils/Errors.sol"; + +import {Flush} from "./utils/Flush.sol"; import {Reentrancy} from "./utils/Reentrancy.sol"; import {SafeERC20} from "./utils/SafeTransferERC20.sol"; @@ -19,7 +21,7 @@ import {SafeERC20} from "./utils/SafeTransferERC20.sol"; /// @author @flocqst /// @author @barrasso /// @author @moss-eth -contract Zap is Reentrancy, Errors { +contract Zap is Reentrancy, Errors, Flush(msg.sender) { /// @custom:circle address public immutable USDC; @@ -454,64 +456,14 @@ contract Zap is Reentrancy, Errors { requireStage(Stage.LEVEL2) returns (uint256 unwound) { - { - ( - uint128 _accountId, - uint128 _collateralId, - uint256 _collateralAmount, - , - , - uint256 _zapMinAmountOut, - uint256 _unwrapMinAmountOut, - , - ) = abi.decode( - _params, - ( - uint128, - uint128, - uint256, - address, - bytes, - uint256, - uint256, - uint256, - address - ) - ); - - // zap USDC from flashloan into USDx; - // ALL USDC flashloaned from Aave is zapped into USDx - uint256 usdxAmount = _zapIn(_flashloan, _zapMinAmountOut); - - // burn USDx to pay off synthetix perp position debt; - // debt is denominated in USD and thus repaid with USDx - _burn(usdxAmount, _accountId); - - /// @dev given the USDC buffer, an amount of USDx - /// necessarily less than the buffer will remain (<$1); - /// this amount is captured by the protocol - // withdraw synthetix perp position collateral to this contract; - // i.e., # of sETH, # of sUSDe, # of sUSDC (...) - _withdraw(_collateralId, _collateralAmount, _accountId); - - // unwrap withdrawn synthetix perp position collateral; - // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) - unwound = - _unwrap(_collateralId, _collateralAmount, _unwrapMinAmountOut); - - // establish total debt now owed to Aave; - // i.e., # of USDC - _flashloan += _premium; - } - ( - , - , - , + uint128 _accountId, + uint128 _collateralId, + uint256 _collateralAmount, address _collateral, bytes memory _path, - , - , + uint256 _zapMinAmountOut, + uint256 _unwrapMinAmountOut, uint256 _swapMaxAmountIn, ) = abi.decode( _params, @@ -528,6 +480,29 @@ contract Zap is Reentrancy, Errors { ) ); + // zap USDC from flashloan into USDx; + // ALL USDC flashloaned from Aave is zapped into USDx + uint256 usdxAmount = _zapIn(_flashloan, _zapMinAmountOut); + + // burn USDx to pay off synthetix perp position debt; + // debt is denominated in USD and thus repaid with USDx + _burn(usdxAmount, _accountId); + + /// @dev given the USDC buffer, an amount of USDx + /// necessarily less than the buffer will remain (<$1); + /// this amount is captured by the protocol + // withdraw synthetix perp position collateral to this contract; + // i.e., # of sETH, # of sUSDe, # of sUSDC (...) + _withdraw(_collateralId, _collateralAmount, _accountId); + + // unwrap withdrawn synthetix perp position collateral; + // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) + unwound = _unwrap(_collateralId, _collateralAmount, _unwrapMinAmountOut); + + // establish total debt now owed to Aave; + // i.e., # of USDC + _flashloan += _premium; + // swap as much (or little) as necessary to repay Aave flashloan; // i.e., WETH -(swap)-> USDC -(repay)-> Aave // i.e., USDe -(swap)-> USDC -(repay)-> Aave @@ -536,6 +511,14 @@ contract Zap is Reentrancy, Errors { unwound -= _collateral == USDC ? _flashloan : _swapFor(_collateral, _path, _flashloan, _swapMaxAmountIn); + + /// @notice the path and max amount in must take into consideration: + /// (1) Aave flashloan amount + /// (2) premium owed to Aave for flashloan + /// (3) USDC buffer added to the approximate loan needed + /// + /// @dev (1) is a function of (3); buffer added to loan requested + /// @dev (2) is a function of (1); premium is a percentage of loan } /// @notice approximate USDC needed to unwind synthetix perp position From d23145addb3cd04455f7fe32762291d9a33d0005 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:35:12 -0400 Subject: [PATCH 116/129] =?UTF-8?q?=F0=9F=9A=80=20update=20deployment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 11 +++++++---- script/utils/Parameters.sol | 9 +++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 6948fe3..93bbb25 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {Script} from "../lib/forge-std/src/Script.sol"; -import {Zap} from "../src/Zap.sol"; +import {Flush, Zap} from "../src/Zap.sol"; import {Arbitrum, ArbitrumSepolia, Base} from "./utils/Parameters.sol"; /// @title zap deployment script @@ -53,7 +53,7 @@ contract Deploy is Script { contract DeployBase is Deploy, Base { function run() public broadcast { - deploySystem({ + Zap zap = deploySystem({ usdc: BASE_USDC, usdx: BASE_USDX, spotMarket: BASE_SPOT_MARKET, @@ -64,6 +64,7 @@ contract DeployBase is Deploy, Base { router: BASE_ROUTER, quoter: BASE_QUOTER }); + Flush(address(zap)).designatePlumber(BASE_PDAO); } } @@ -76,7 +77,7 @@ contract DeployBase is Deploy, Base { contract DeployArbitrum is Deploy, Arbitrum { function run() public broadcast { - deploySystem({ + Zap zap = deploySystem({ usdc: ARBITRUM_USDC, usdx: ARBITRUM_USDX, spotMarket: ARBITRUM_SPOT_MARKET, @@ -87,6 +88,7 @@ contract DeployArbitrum is Deploy, Arbitrum { router: ARBITRUM_ROUTER, quoter: ARBITRUM_QUOTER }); + Flush(address(zap)).designatePlumber(ARBITRUM_PDAO); } } @@ -99,7 +101,7 @@ contract DeployArbitrum is Deploy, Arbitrum { contract DeployArbitrumSepolia is Deploy, ArbitrumSepolia { function run() public broadcast { - deploySystem({ + Zap zap = deploySystem({ usdc: ARBITRUM_SEPOLIA_USDC, usdx: ARBITRUM_SEPOLIA_USDX, spotMarket: ARBITRUM_SEPOLIA_SPOT_MARKET, @@ -110,6 +112,7 @@ contract DeployArbitrumSepolia is Deploy, ArbitrumSepolia { router: ARBITRUM_SEPOLIA_ROUTER, quoter: ARBITRUM_SEPOLIA_QUOTER }); + Flush(address(zap)).designatePlumber(ARBITRUM_SEPOLIA_PDAO); } } diff --git a/script/utils/Parameters.sol b/script/utils/Parameters.sol index a1aaced..f63a677 100644 --- a/script/utils/Parameters.sol +++ b/script/utils/Parameters.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.27; contract Base { + /// @custom:pdao + address BASE_PDAO = 0xbb63CA5554dc4CcaCa4EDd6ECC2837d5EFe83C82; + /// @custom:synthetix address BASE_USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; address BASE_USDX = 0x09d51516F38980035153a554c26Df3C6f51a23C3; @@ -22,6 +25,9 @@ contract Base { contract Arbitrum { + /// @custom:pdao + address ARBITRUM_PDAO = 0xD3DFa13CDc7c133b1700c243f03A8C6Df513A93b; + /// @custom:synthetix address ARBITRUM_USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; address ARBITRUM_USDX = 0xb2F30A7C980f052f02563fb518dcc39e6bf38175; @@ -41,6 +47,9 @@ contract Arbitrum { contract ArbitrumSepolia { + /// @custom:pdao + address ARBITRUM_SEPOLIA_PDAO = address(0); + /// @custom:synthetix address ARBITRUM_SEPOLIA_USDC = 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d; address ARBITRUM_SEPOLIA_USDX = 0xe487Ad4291019b33e2230F8E2FB1fb6490325260; From 2fb5e1cb4e4f1cd63f16c4e2c36259adeb1a8b86 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:38:10 -0400 Subject: [PATCH 117/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 973ec51..eb295fd 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,8 @@ "license": "GPL-3.0-or-later", "homepage": "https://github.com/JaredBorders/zap#readme", "scripts": { - "compile": "forge build", - "test": "forge test --gas-report -vvv", - "format": "forge fmt", - "coverage": "forge coverage", - "coverage:generate-lcov": "forge coverage --report lcov", - "analysis:slither": "slither .", - "gas-snapshot": "forge snapshot", - "decode-custom-error": "npx @usecannon/cli decode synthetix-perps-market" + "coverage:generate-lcov": "forge coverage --report lcov --ir-minimum", + "gas-snapshot": "forge snapshot" }, "repository": { "type": "git", From f00cd7d60fdfad96cb5990080c6551e46c76ae7b Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:39:05 -0400 Subject: [PATCH 118/129] =?UTF-8?q?=F0=9F=93=B8=20update=20lcov/gas-snapsh?= =?UTF-8?q?ot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 70 +++++------ lcov.info | 329 +++++++++++++++++++++++++------------------------- 2 files changed, 200 insertions(+), 199 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 6950c02..c207c47 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,35 +1,35 @@ -AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 256, μ: 3908016, ~: 3908015) -BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 256, μ: 3859004, ~: 3859004) -BuyTest:test_buy_arbitrum(uint32) (runs: 256, μ: 4467739, ~: 4467739) -BuyTest:test_buy_base(uint32) (runs: 256, μ: 4387367, ~: 4387367) -FlushTest:test_flush(uint32) (runs: 256, μ: 3859026, ~: 3859026) -ReentrancyTest:test_requireStage(uint8) (runs: 256, μ: 15532, ~: 6343) -ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 16427, ~: 8897) -ReentrancyTest:test_stage_default() (gas: 2435) -ReentrancyTest:test_stage_enum_level1() (gas: 250) -ReentrancyTest:test_stage_enum_level2() (gas: 294) -ReentrancyTest:test_stage_enum_unset() (gas: 293) -SellTest:test_sell_arbitrum(uint32) (runs: 256, μ: 4635354, ~: 4635354) -SellTest:test_sell_base(uint32) (runs: 256, μ: 4536794, ~: 4536794) -SwapForTest:test_swap_for_multihop_arbitrum() (gas: 4346260) -SwapForTest:test_swap_for_multihop_base() (gas: 4348510) -SwapForTest:test_swap_for_single_arbitrum(uint8) (runs: 256, μ: 4194700, ~: 4194700) -SwapForTest:test_swap_for_single_arbitrum_sepolia() (gas: 3858759) -SwapForTest:test_swap_for_single_base() (gas: 4208679) -SwapWithTest:test_swap_with_arbitrum(uint8) (runs: 256, μ: 4168946, ~: 4168946) -SwapWithTest:test_swap_with_arbitrum_sepolia() (gas: 3858803) -SwapWithTest:test_swap_with_base(uint8) (runs: 256, μ: 4102090, ~: 4102090) -UnwindTest:test_unwind_arbitrum_sepolia() (gas: 3858736) -UnwindTest:test_unwind_is_authorized() (gas: 3926493) -UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 256, μ: 4679724, ~: 4679724) -UnwrapTest:test_unwrap_base(uint32) (runs: 256, μ: 4571660, ~: 4571660) -WithdrawTest:test_withdraw_arbitrum() (gas: 4751268) -WithdrawTest:test_withdraw_base() (gas: 4574617) -WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 3924093) -WithdrawTest:test_withdraw_is_authorized_base() (gas: 3924092) -WrapTest:test_wrap_arbitrum(uint32) (runs: 256, μ: 4503212, ~: 4503212) -WrapTest:test_wrap_base(uint32) (runs: 256, μ: 4412793, ~: 4412793) -ZapInTest:test_zap_in_arbitrum(uint32) (runs: 256, μ: 4636324, ~: 4636324) -ZapInTest:test_zap_in_base(uint32) (runs: 256, μ: 4533297, ~: 4533297) -ZapOutTest:test_zap_out_arbitum(uint64) (runs: 256, μ: 4609235, ~: 4609235) -ZapOutTest:test_zap_out_base(uint64) (runs: 256, μ: 4511128, ~: 4511128) \ No newline at end of file +AaveTest:test_executeOperation_only_aave(address,address,uint256,uint256,bytes) (runs: 256, μ: 4072994, ~: 4072993) +BurnTest:test_burn_arbitrum_sepolia(uint32) (runs: 256, μ: 4021378, ~: 4021378) +BuyTest:test_buy_arbitrum(uint32) (runs: 256, μ: 4712117, ~: 4712117) +BuyTest:test_buy_base(uint32) (runs: 256, μ: 4631652, ~: 4631652) +FlushTest:test_flush(uint32) (runs: 256, μ: 4021401, ~: 4021401) +ReentrancyTest:test_requireStage(uint8) (runs: 256, μ: 17762, ~: 8622) +ReentrancyTest:test_requireStage_throws(uint8,uint256) (runs: 256, μ: 18839, ~: 11503) +ReentrancyTest:test_stage_default() (gas: 3027) +ReentrancyTest:test_stage_enum_level1() (gas: 660) +ReentrancyTest:test_stage_enum_level2() (gas: 706) +ReentrancyTest:test_stage_enum_unset() (gas: 844) +SellTest:test_sell_arbitrum(uint32) (runs: 256, μ: 4884336, ~: 4884336) +SellTest:test_sell_base(uint32) (runs: 256, μ: 4785869, ~: 4785869) +SwapForTest:test_swap_for_multihop_arbitrum() (gas: 4611852) +SwapForTest:test_swap_for_multihop_base() (gas: 4614101) +SwapForTest:test_swap_for_single_arbitrum(uint8) (runs: 256, μ: 4461856, ~: 4461856) +SwapForTest:test_swap_for_single_arbitrum_sepolia() (gas: 4021185) +SwapForTest:test_swap_for_single_base() (gas: 4435614) +SwapWithTest:test_swap_with_arbitrum(uint8) (runs: 256, μ: 4434036, ~: 4434036) +SwapWithTest:test_swap_with_arbitrum_sepolia() (gas: 4021231) +SwapWithTest:test_swap_with_base(uint8) (runs: 256, μ: 4328655, ~: 4328655) +UnwindTest:test_unwind_arbitrum_sepolia() (gas: 4021254) +UnwindTest:test_unwind_is_authorized() (gas: 4091114) +UnwrapTest:test_unwrap_arbitrum(uint32) (runs: 256, μ: 4948844, ~: 4948844) +UnwrapTest:test_unwrap_base(uint32) (runs: 256, μ: 4840687, ~: 4840687) +WithdrawTest:test_withdraw_arbitrum() (gas: 4996101) +WithdrawTest:test_withdraw_base() (gas: 4819562) +WithdrawTest:test_withdraw_is_authorized_arbitrum() (gas: 4089866) +WithdrawTest:test_withdraw_is_authorized_base() (gas: 4089981) +WrapTest:test_wrap_arbitrum(uint32) (runs: 256, μ: 4766711, ~: 4766711) +WrapTest:test_wrap_base(uint32) (runs: 256, μ: 4676382, ~: 4676382) +ZapInTest:test_zap_in_arbitrum(uint32) (runs: 256, μ: 4898567, ~: 4898567) +ZapInTest:test_zap_in_base(uint32) (runs: 256, μ: 4795630, ~: 4795630) +ZapOutTest:test_zap_out_arbitum(uint64) (runs: 256, μ: 4853363, ~: 4853363) +ZapOutTest:test_zap_out_base(uint64) (runs: 256, μ: 4755166, ~: 4755166) \ No newline at end of file diff --git a/lcov.info b/lcov.info index 73f9cb2..1096b2a 100644 --- a/lcov.info +++ b/lcov.info @@ -6,228 +6,229 @@ DA:14,0 DA:15,0 DA:17,0 FN:20,Deploy.deploySystem -FNDA:0,Deploy.deploySystem +FNDA:4619,Deploy.deploySystem DA:34,4619 FN:55,DeployBase.run FNDA:0,DeployBase.run DA:56,0 -FN:78,DeployArbitrum.run +DA:67,0 +FN:79,DeployArbitrum.run FNDA:0,DeployArbitrum.run -DA:79,0 -FN:101,DeployArbitrumSepolia.run +DA:80,0 +DA:91,0 +FN:103,DeployArbitrumSepolia.run FNDA:0,DeployArbitrumSepolia.run -DA:102,0 +DA:104,0 +DA:115,0 FNF:5 -FNH:0 -LF:7 +FNH:1 +LF:10 LH:1 BRF:0 BRH:0 end_of_record TN: SF:src/Zap.sol -FN:46,Zap. +FN:48,Zap. FNDA:4619,Zap. -DA:58,4619 -DA:61,4619 -DA:62,4619 +DA:60,4619 DA:63,4619 DA:64,4619 DA:65,4619 -DA:68,4619 -DA:71,4619 -DA:72,4619 -FN:81,Zap.isAuthorized +DA:66,4619 +DA:67,4619 +DA:70,4619 +DA:73,4619 +DA:74,4619 +FN:83,Zap.isAuthorized FNDA:1,Zap.isAuthorized -DA:82,1 -DA:85,1 -BRDA:85,0,0,2 -BRDA:85,0,1,2 -FN:90,Zap.onlyAave +DA:84,1 +DA:87,1 +BRDA:87,0,0,- +BRDA:87,0,1,- +FN:92,Zap.onlyAave FNDA:256,Zap.onlyAave -DA:91,256 -BRDA:91,1,0,256 -BRDA:91,1,1,- -FN:105,Zap.zapIn +DA:93,256 +BRDA:93,1,0,- +BRDA:93,1,1,- +FN:107,Zap.zapIn FNDA:512,Zap.zapIn -DA:113,512 -DA:114,512 DA:115,512 -FN:120,Zap._zapIn +DA:116,512 +DA:117,512 +FN:122,Zap._zapIn FNDA:512,Zap._zapIn -DA:127,512 -DA:128,512 -FN:137,Zap.zapOut +DA:129,512 +DA:130,512 +FN:139,Zap.zapOut FNDA:512,Zap.zapOut -DA:145,512 -DA:146,512 DA:147,512 -FN:152,Zap._zapOut +DA:148,512 +DA:149,512 +FN:154,Zap._zapOut FNDA:512,Zap._zapOut -DA:159,512 -DA:160,512 -FN:176,Zap.wrap +DA:161,512 +DA:162,512 +FN:178,Zap.wrap FNDA:1024,Zap.wrap -DA:186,1024 -DA:187,1024 DA:188,1024 -FN:193,Zap._wrap +DA:189,1024 +DA:190,1024 +FN:195,Zap._wrap FNDA:1536,Zap._wrap -DA:202,1536 -DA:203,1536 -FN:219,Zap.unwrap +DA:204,1536 +DA:205,1536 +FN:221,Zap.unwrap FNDA:512,Zap.unwrap -DA:229,512 -DA:230,512 DA:231,512 DA:232,512 -FN:237,Zap._unwrap +DA:233,512 +DA:234,512 +FN:239,Zap._unwrap FNDA:1024,Zap._unwrap -DA:245,1024 -DA:246,1024 DA:247,1024 -FN:265,Zap.buy +DA:248,1024 +DA:249,1024 +FN:267,Zap.buy FNDA:1024,Zap.buy -DA:274,1024 -DA:275,1024 DA:276,1024 DA:277,1024 -FN:282,Zap._buy +DA:278,1024 +DA:279,1024 +FN:284,Zap._buy FNDA:1536,Zap._buy -DA:290,1536 -DA:291,1536 -FN:306,Zap.sell +DA:292,1536 +DA:293,1536 +FN:308,Zap.sell FNDA:512,Zap.sell -DA:315,512 -DA:316,512 DA:317,512 DA:318,512 -FN:323,Zap._sell +DA:319,512 +DA:320,512 +FN:325,Zap._sell FNDA:1024,Zap._sell -DA:331,1024 -DA:332,1024 DA:333,1024 -FN:357,Zap.unwind -FNDA:1,Zap.unwind -DA:372,0 +DA:334,1024 +DA:335,1024 +FN:359,Zap.unwind +FNDA:0,Zap.unwind DA:374,0 -DA:387,0 +DA:376,0 DA:389,0 -DA:397,0 -FN:407,Zap.executeOperation -FNDA:256,Zap.executeOperation -DA:419,0 +DA:391,0 +DA:399,0 +FN:409,Zap.executeOperation +FNDA:0,Zap.executeOperation DA:421,0 -DA:436,0 +DA:423,0 DA:438,0 DA:440,0 -FN:448,Zap._unwind +DA:442,0 +FN:450,Zap._unwind FNDA:0,Zap._unwind -DA:458,0 -DA:467,0 -DA:484,0 -DA:488,0 -DA:495,0 -DA:499,0 +DA:459,0 +DA:468,0 +DA:485,0 +DA:489,0 +DA:496,0 +DA:500,0 DA:504,0 -DA:507,0 -DA:516,0 -DA:536,0 -FN:544,Zap._approximateLoanNeeded +DA:511,0 +FN:527,Zap._approximateLoanNeeded FNDA:0,Zap._approximateLoanNeeded -DA:550,0 -DA:552,0 -DA:553,0 -DA:558,0 -DA:563,0 -FN:577,Zap.burn +DA:533,0 +DA:535,0 +DA:536,0 +DA:541,0 +DA:546,0 +FN:560,Zap.burn FNDA:0,Zap.burn -DA:584,0 -DA:587,0 -DA:588,0 -DA:590,0 -DA:592,0 -BRDA:592,2,0,- -FN:597,Zap._burn +DA:567,0 +DA:570,0 +DA:571,0 +DA:573,0 +DA:575,0 +BRDA:575,2,0,- +FN:580,Zap._burn FNDA:0,Zap._burn -DA:598,0 -DA:599,0 -DA:600,0 -FN:613,Zap.withdraw -FNDA:4,Zap.withdraw +DA:581,0 +DA:582,0 +DA:583,0 +FN:596,Zap.withdraw +FNDA:2,Zap.withdraw +DA:605,2 +DA:606,2 +DA:609,2 +FN:615,Zap._withdraw +FNDA:2,Zap._withdraw DA:622,2 DA:623,2 -DA:626,2 -FN:632,Zap._withdraw -FNDA:2,Zap._withdraw -DA:639,2 -DA:640,2 -DA:645,2 -FN:660,Zap.quoteSwapFor +DA:628,2 +FN:643,Zap.quoteSwapFor FNDA:0,Zap.quoteSwapFor -DA:672,0 -FN:683,Zap.quoteSwapWith +DA:655,0 +FN:666,Zap.quoteSwapWith FNDA:0,Zap.quoteSwapWith -DA:695,0 -FN:708,Zap.swapFor +DA:678,0 +FN:691,Zap.swapFor FNDA:259,Zap.swapFor -DA:718,259 -DA:719,259 -DA:720,3 -DA:722,3 -BRDA:722,3,0,3 -DA:723,3 -FN:729,Zap._swapFor +DA:701,259 +DA:702,259 +DA:703,259 +DA:705,3 +BRDA:705,3,0,3 +DA:706,3 +FN:712,Zap._swapFor FNDA:259,Zap._swapFor -DA:738,259 -DA:740,259 -DA:747,259 -DA:748,3 -DA:749,256 -DA:750,256 -DA:753,3 -FN:767,Zap.swapWith +DA:721,259 +DA:723,259 +DA:730,259 +DA:731,3 +DA:732,256 +DA:733,256 +DA:736,0 +FN:750,Zap.swapWith FNDA:512,Zap.swapWith -DA:777,512 -DA:778,0 -DA:779,0 -FN:784,Zap._swapWith +DA:760,512 +DA:761,512 +DA:762,512 +FN:767,Zap._swapWith FNDA:0,Zap._swapWith -DA:793,0 -DA:795,0 -DA:802,0 -DA:803,0 -DA:804,0 -DA:805,0 -FN:817,Zap._pull +DA:776,0 +DA:778,0 +DA:785,0 +DA:786,0 +DA:787,0 +DA:788,0 +FN:800,Zap._pull FNDA:4867,Zap._pull -DA:818,4867 -BRDA:818,4,0,512 -BRDA:818,4,1,4355 -DA:819,4355 -DA:821,4355 -FN:828,Zap._push +DA:801,4867 +BRDA:801,4,0,- +BRDA:801,4,1,- +DA:802,4867 +DA:804,4867 +FN:811,Zap._push FNDA:4104,Zap._push -DA:835,4104 -BRDA:835,5,0,- -BRDA:835,5,1,4104 -DA:836,4104 -BRDA:836,6,0,- -BRDA:836,6,1,4104 -DA:837,4104 -DA:839,4104 +DA:818,4104 +BRDA:818,5,0,- +BRDA:818,5,1,- +DA:819,4104 +BRDA:819,6,0,- +BRDA:819,6,1,- +DA:820,4104 +DA:822,4104 FNF:31 -FNH:24 -LF:116 -LH:73 +FNH:22 +LF:114 +LH:74 BRF:12 -BRH:8 +BRH:1 end_of_record TN: SF:src/utils/Flush.sol FN:19,Flush. -FNDA:0,Flush. -DA:20,0 +FNDA:4619,Flush. +DA:20,4619 FN:26,Flush.flush FNDA:0,Flush.flush DA:27,0 @@ -245,9 +246,9 @@ BRDA:38,2,1,- DA:39,0 DA:40,0 FNF:3 -FNH:0 +FNH:1 LF:8 -LH:0 +LH:1 BRF:5 BRH:0 end_of_record @@ -259,14 +260,14 @@ DA:28,0 FN:32,Reentrancy._requireStage FNDA:424,Reentrancy._requireStage DA:33,424 -BRDA:33,0,0,168 -BRDA:33,0,1,256 +BRDA:33,0,0,- +BRDA:33,0,1,- FNF:2 FNH:1 LF:2 LH:1 BRF:2 -BRH:2 +BRH:0 end_of_record TN: SF:src/utils/SafeTransferERC20.sol @@ -280,14 +281,14 @@ FN:67,SafeERC20._callOptionalReturn FNDA:8459,SafeERC20._callOptionalReturn DA:68,8459 DA:69,8459 -DA:71,8459 -DA:74,8459 +DA:71,0 +DA:74,0 BRDA:74,0,0,- DA:75,0 DA:76,0 DA:77,0 -DA:79,8459 -DA:80,8459 +DA:79,0 +DA:80,0 DA:84,8459 DA:85,0 BRDA:85,1,0,- @@ -295,7 +296,7 @@ DA:86,0 FNF:3 FNH:3 LF:14 -LH:9 +LH:5 BRF:2 BRH:0 end_of_record From 90851bfd9cab2922ec561c96ba16c817611a4629 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:25:07 -0400 Subject: [PATCH 119/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20.gitignor?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9270291..138f0f1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ out/ # Other .vscode .idea +.DS-store From d1e7aeba09a4e810ee31c7d26eee6ac54a10bd70 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:25:43 -0400 Subject: [PATCH 120/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20env=20exa?= =?UTF-8?q?mple?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-example | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.env-example b/.env-example index 28a50b6..f143e60 100644 --- a/.env-example +++ b/.env-example @@ -1,6 +1,7 @@ -BASE_RPC=https://base-mainnet.g.alchemy.com/v2/KEY -ARBITRUM_RPC=https://arb-mainnet.g.alchemy.com/v2/KEY -ARBITRUM_SEPOLIA_RPC=https://arb-sepolia.g.alchemy.com/v2/KEY -PRIVATE_KEY=KEY -BASESCAN_API_KEY=KEY -ARBISCAN_API_KEY=KEY \ No newline at end of file +BASE_RPC=https://base-mainnet.g.alchemy.com/v2/ +ARBITRUM_RPC=https://arb-mainnet.g.alchemy.com/v2/ +ARBITRUM_SEPOLIA_RPC=https://arb-sepolia.g.alchemy.com/v2/ +BASESCAN_API_KEY= +ARBISCAN_API_KEY= +ARBISCAN_SEPOLIA_API_KEY= +PRIVATE_KEY= \ No newline at end of file From e2bfdd3f8f473c8cbe53f4354fa86a21c610cfb7 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:26:04 -0400 Subject: [PATCH 121/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20foundy=20?= =?UTF-8?q?config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- foundry.toml | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/foundry.toml b/foundry.toml index 66303e6..2403b24 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ out = 'out' libs = ['lib'] solc_version = "0.8.27" optimizer = true -optimizer_runs = 1_000_000 +optimizer_runs = 500_000 via-ir = true [fmt] @@ -18,17 +18,9 @@ override_spacing = false wrap_comments = true [rpc_endpoints] -mainnet = "${MAINNET_RPC_URL}" -optimism = "${OPTIMISM_RPC_URL}" -base = "${BASE_RPC_URL}" -goerli = "${GOERLI_RPC_URL}" -optimismGoerli = "${OPTIMISM_GOERLI_RPC_URL}" -baseGoerli = "${BASE_GOERLI_RPC_URL}" +arbitrum = "${ARBITRUM_RPC}" +base = "${BASE_RPC}" [etherscan] -mainnet = { key = "${ETHERSCAN_API_KEY}" } -optimism = { key = "${OPTIMISM_ETHERSCAN_API_KEY}" } -base = { key = "${BASESCAN_API_KEY}" } -goerli = { key = "${ETHERSCAN_API_KEY}" } -optimismGoerli = { key = "${OPTIMISM_ETHERSCAN_API_KEY}" } -baseGoerli = { key = "${BASESCAN_API_KEY}" } \ No newline at end of file +arbitrum = { key = "${ARBISCAN_API_KEY}" } +base = { key = "${BASESCAN_API_KEY}" } \ No newline at end of file From 9479f699b65e9060e08a6026c49b9090cb13a760 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:26:31 -0400 Subject: [PATCH 122/129] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20add=20makefile=20f?= =?UTF-8?q?or=20deployment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 makefile diff --git a/makefile b/makefile new file mode 100644 index 0000000..7909907 --- /dev/null +++ b/makefile @@ -0,0 +1,11 @@ +deploy_base: + source .env && forge script --chain base script/Deploy.s.sol:DeployArbitrum --rpc-url $$BASE_RPC --broadcast --verify -vvvv + +deploy_arbitrum: + source .env && forge script --chain arbitrum script/Deploy.s.sol:DeployArbitrum --rpc-url $$ARBITRUM_RPC --broadcast --verify -vvvv + +coverage: + forge coverage --report lcov --ir-minimum + +snapshot: + forge snapshot From 406b585c6f386583033be3ff189543b585299733 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:26:51 -0400 Subject: [PATCH 123/129] =?UTF-8?q?=F0=9F=9A=80=20update=20deploy=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/Deploy.s.sol | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 93bbb25..038fc27 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -46,10 +46,7 @@ contract Deploy is Script { } -/// @dev steps to deploy and verify on Base: -/// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployBase --rpc-url $BASE_RPC_URL -/// --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv` +/// @custom:deploy `make deploy_base` contract DeployBase is Deploy, Base { function run() public broadcast { @@ -69,11 +66,7 @@ contract DeployBase is Deploy, Base { } -/// @dev steps to deploy and verify on Arbitrum: -/// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployArbitrum --rpc-url -/// $ARBITRUM_RPC_URL -/// --etherscan-api-key $ARBITRUM_RPC_URL --broadcast --verify -vvvv` +/// @custom:deplo `make deploy_arbitrum` contract DeployArbitrum is Deploy, Arbitrum { function run() public broadcast { @@ -93,11 +86,7 @@ contract DeployArbitrum is Deploy, Arbitrum { } -/// @dev steps to deploy and verify on Arbitrum: -/// (1) load the variables in the .env file via `source .env` -/// (2) run `forge script script/Deploy.s.sol:DeployArbitrumSepolia --rpc-url -/// $ARBITRUM_SEPOLIA_RPC_URL -/// --etherscan-api-key $ARBITRUM_RPC_URL --broadcast --verify -vvvv` +/// @custom:deploy `make deploy_arbitrum_sepolia` contract DeployArbitrumSepolia is Deploy, ArbitrumSepolia { function run() public broadcast { From 96d9fb24ab33b2f7727b5f5d1d188dc998822b41 Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:27:06 -0400 Subject: [PATCH 124/129] =?UTF-8?q?=F0=9F=93=9A=20update=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6abd473..deedbed 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Zap Flashloan Utility -[![Github Actions][gha-badge]][gha] -[![Foundry][foundry-badge]][foundry] +[![Github Actions][gha-badge]][gha] +[![Foundry][foundry-badge]][foundry] [![License: GPL-3.0][license-badge]][license] [gha]: https://github.com/JaredBorders/Zap/actions @@ -16,6 +16,7 @@ Zap is a smart contract that facilitates stablecoin "zapping" and Synthetix native collateral unwinding by integrating flash loans from Aave and Uniswap v3 swaps. ### What is a **Zap**? + > ONLY USDC is supported USDC <--(spot market)--> sUSDC <--(spot market)--> USDx @@ -42,24 +43,50 @@ USDC <--(spot market)--> sUSDC <--(spot market)--> USDx - Withdraw Perp Collateral via Synthetix - Swap via Uniswap -## Build and Test +## Development + +### Build, Test, Report -### Build and Run +1. **Build the project** -1. **Build the project** ```bash forge build ``` -2. **Run tests** +2. **Run tests** + ```bash forge test ``` -## Deployment +3. **Report Test Coverage** + + ```bash + make coverage + ``` + +4. **Report Gas Snapshot** + ```bash + make snapshot + ``` + +## How to Deploy - See the `deployments/` folder for Arbitrum and Base deployments. +How to Deploy: + +1. Create a `.env` file using `.env-example` as a reference + +2. Deploy to Base + ```bash + make deploy_base + ``` +3. Deploy to Arbitrum + ```bash + make deploy_arbitrum + ``` + ## Audits - See the `audits/` folder for Audit reports. From a86de8d4e7d8111892f5c2ac08be4e4b4415458f Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:27:23 -0400 Subject: [PATCH 125/129] =?UTF-8?q?=F0=9F=9A=80=20add=20deployments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployments/Arbitrum.json | 3 +++ deployments/Base.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/deployments/Arbitrum.json b/deployments/Arbitrum.json index e69de29..909288b 100644 --- a/deployments/Arbitrum.json +++ b/deployments/Arbitrum.json @@ -0,0 +1,3 @@ +{ + "Zap": "0xD1F129e0cDd3Cc5c65ea00041623841C3d709F83" +} \ No newline at end of file diff --git a/deployments/Base.json b/deployments/Base.json index e69de29..3e9b2f1 100644 --- a/deployments/Base.json +++ b/deployments/Base.json @@ -0,0 +1,3 @@ +{ + "Zap": "0xC9aF789Ae606F69cF8Ed073A04eC92f2354b027d" +} \ No newline at end of file From 70d3ea131ffe8af2f978b53f91daa0d8ac74d19a Mon Sep 17 00:00:00 2001 From: wasp-in-bush <177558793+wasp-in-bush@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:34:08 -0400 Subject: [PATCH 126/129] =?UTF-8?q?=F0=9F=93=9A=20update=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index deedbed..645e85d 100644 --- a/README.md +++ b/README.md @@ -49,24 +49,24 @@ USDC <--(spot market)--> sUSDC <--(spot market)--> USDx 1. **Build the project** - ```bash + ``` forge build ``` 2. **Run tests** - ```bash + ``` forge test ``` 3. **Report Test Coverage** - ```bash + ``` make coverage ``` 4. **Report Gas Snapshot** - ```bash + ``` make snapshot ``` @@ -79,11 +79,11 @@ How to Deploy: 1. Create a `.env` file using `.env-example` as a reference 2. Deploy to Base - ```bash + ``` make deploy_base ``` 3. Deploy to Arbitrum - ```bash + ``` make deploy_arbitrum ``` From 356a4559f4dc675ea6c58beb6c3f2d138a9e020d Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 22 Oct 2024 10:58:59 -0400 Subject: [PATCH 127/129] =?UTF-8?q?=F0=9F=93=9A=20forge=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/README.md | 45 +++++++++++++++---- docs/src/src/Zap.sol/contract.Zap.md | 17 +++++-- .../interfaces/IAave.sol/interface.IPool.md | 2 +- .../interfaces/IERC20.sol/interface.IERC20.md | 2 +- .../ISynthetix.sol/interface.IPerpsMarket.md | 2 +- .../ISynthetix.sol/interface.ISpotMarket.md | 2 +- .../IUniswap.sol/interface.IQuoter.md | 2 +- .../IUniswap.sol/interface.IRouter.md | 2 +- .../src/utils/Errors.sol/contract.Errors.md | 2 +- .../src/src/utils/Flush.sol/contract.Flush.md | 2 +- .../Reentrancy.sol/contract.Reentrancy.md | 2 +- .../library.SafeERC20.md | 2 +- 12 files changed, 59 insertions(+), 23 deletions(-) diff --git a/docs/src/README.md b/docs/src/README.md index 6abd473..645e85d 100644 --- a/docs/src/README.md +++ b/docs/src/README.md @@ -1,7 +1,7 @@ # Zap Flashloan Utility -[![Github Actions][gha-badge]][gha] -[![Foundry][foundry-badge]][foundry] +[![Github Actions][gha-badge]][gha] +[![Foundry][foundry-badge]][foundry] [![License: GPL-3.0][license-badge]][license] [gha]: https://github.com/JaredBorders/Zap/actions @@ -16,6 +16,7 @@ Zap is a smart contract that facilitates stablecoin "zapping" and Synthetix native collateral unwinding by integrating flash loans from Aave and Uniswap v3 swaps. ### What is a **Zap**? + > ONLY USDC is supported USDC <--(spot market)--> sUSDC <--(spot market)--> USDx @@ -42,24 +43,50 @@ USDC <--(spot market)--> sUSDC <--(spot market)--> USDx - Withdraw Perp Collateral via Synthetix - Swap via Uniswap -## Build and Test +## Development + +### Build, Test, Report -### Build and Run +1. **Build the project** -1. **Build the project** - ```bash + ``` forge build ``` -2. **Run tests** - ```bash +2. **Run tests** + + ``` forge test ``` -## Deployment +3. **Report Test Coverage** + + ``` + make coverage + ``` + +4. **Report Gas Snapshot** + ``` + make snapshot + ``` + +## How to Deploy - See the `deployments/` folder for Arbitrum and Base deployments. +How to Deploy: + +1. Create a `.env` file using `.env-example` as a reference + +2. Deploy to Base + ``` + make deploy_base + ``` +3. Deploy to Arbitrum + ``` + make deploy_arbitrum + ``` + ## Audits - See the `audits/` folder for Audit reports. diff --git a/docs/src/src/Zap.sol/contract.Zap.md b/docs/src/src/Zap.sol/contract.Zap.md index 32aa944..f42277a 100644 --- a/docs/src/src/Zap.sol/contract.Zap.md +++ b/docs/src/src/Zap.sol/contract.Zap.md @@ -1,8 +1,8 @@ # Zap -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/Zap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/Zap.sol) **Inherits:** -[Reentrancy](/src/utils/Reentrancy.sol/contract.Reentrancy.md), [Errors](/src/utils/Errors.sol/contract.Errors.md) +[Reentrancy](/src/utils/Reentrancy.sol/contract.Reentrancy.md), [Errors](/src/utils/Errors.sol/contract.Errors.md), [Flush](/src/utils/Flush.sol/contract.Flush.md) **Authors:** @jaredborders, @flocqst, @barrasso, @moss-eth @@ -560,12 +560,21 @@ function _unwind( ### _approximateLoanNeeded +the path and max amount in must take into consideration: +(1) Aave flashloan amount +(2) premium owed to Aave for flashloan +(3) USDC buffer added to the approximate loan needed + approximate USDC needed to unwind synthetix perp position *given the USDC buffer, an amount of USDx necessarily less than the buffer will remain (<$1); this amount is captured by the protocol* +*(1) is a function of (3); buffer added to loan requested* + +*(2) is a function of (1); premium is a percentage of loan* + ```solidity function _approximateLoanNeeded(uint128 _accountId) @@ -607,7 +616,7 @@ function burn( uint128 _accountId ) external - returns (uint256 remaining); + returns (uint256 excess); ``` **Parameters** @@ -620,7 +629,7 @@ function burn( |Name|Type|Description| |----|----|-----------| -|`remaining`|`uint256`|amount of USDx returned to the caller| +|`excess`|`uint256`|amount of USDx returned to the caller| ### _burn diff --git a/docs/src/src/interfaces/IAave.sol/interface.IPool.md b/docs/src/src/interfaces/IAave.sol/interface.IPool.md index dcdd649..61a1769 100644 --- a/docs/src/src/interfaces/IAave.sol/interface.IPool.md +++ b/docs/src/src/interfaces/IAave.sol/interface.IPool.md @@ -1,5 +1,5 @@ # IPool -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/IAave.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/IAave.sol) ## Functions diff --git a/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md b/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md index 696efb9..f77b786 100644 --- a/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md +++ b/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md @@ -1,5 +1,5 @@ # IERC20 -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/IERC20.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/IERC20.sol) ## Functions diff --git a/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md b/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md index 26b99c9..0249c37 100644 --- a/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md +++ b/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md @@ -1,5 +1,5 @@ # IPerpsMarket -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/ISynthetix.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/ISynthetix.sol) ## Functions diff --git a/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md b/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md index 93bba4c..af986f1 100644 --- a/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md +++ b/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md @@ -1,5 +1,5 @@ # ISpotMarket -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/ISynthetix.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/ISynthetix.sol) ## Functions diff --git a/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md b/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md index e9be6bf..cfdca75 100644 --- a/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md +++ b/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md @@ -1,5 +1,5 @@ # IQuoter -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/IUniswap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/IUniswap.sol) ## Functions diff --git a/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md b/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md index 844c9d2..75b817e 100644 --- a/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md +++ b/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md @@ -1,5 +1,5 @@ # IRouter -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/interfaces/IUniswap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/IUniswap.sol) ## Functions diff --git a/docs/src/src/utils/Errors.sol/contract.Errors.md b/docs/src/src/utils/Errors.sol/contract.Errors.md index f05a082..52093c8 100644 --- a/docs/src/src/utils/Errors.sol/contract.Errors.md +++ b/docs/src/src/utils/Errors.sol/contract.Errors.md @@ -1,5 +1,5 @@ # Errors -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/utils/Errors.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/utils/Errors.sol) **Author:** @jaredborders diff --git a/docs/src/src/utils/Flush.sol/contract.Flush.md b/docs/src/src/utils/Flush.sol/contract.Flush.md index 7ba6cf2..96524b4 100644 --- a/docs/src/src/utils/Flush.sol/contract.Flush.md +++ b/docs/src/src/utils/Flush.sol/contract.Flush.md @@ -1,5 +1,5 @@ # Flush -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/utils/Flush.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/utils/Flush.sol) **Author:** @jaredborders diff --git a/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md b/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md index de8444e..a4c30b0 100644 --- a/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md +++ b/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md @@ -1,5 +1,5 @@ # Reentrancy -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/utils/Reentrancy.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/utils/Reentrancy.sol) **Authors:** @moss-eth, @jaredborders diff --git a/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md b/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md index e5fc33d..48dfd16 100644 --- a/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md +++ b/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md @@ -1,5 +1,5 @@ # SafeERC20 -[Git Source](https://github.com/moss-eth/zap/blob/d8297745ea2933770a4e56a10de9706c3d09942b/src/utils/SafeTransferERC20.sol) +[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/utils/SafeTransferERC20.sol) *Wrappers around ERC-20 operations that throw on failure (when the token contract returns false). Tokens that return no value (and instead revert or From c8292c5b05db814229b237bee499b2f96e93ae2f Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 5 Dec 2024 11:44:48 +0900 Subject: [PATCH 128/129] =?UTF-8?q?=F0=9F=91=B7=20Odos=20integration=20(#5?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 👷 wip odos swap * forge install: surl * ⚙️ add ffi to foundry.toml * 👷 added odos router to zap constructor * ✅ successful odos quote using SURL * ✅ arb quote test and fmt * ✅ successfully calling odosAssemble * ✅ wip odos testing * ✨ Lint * ✅ blocked on testing * ✅ test failing in the odos swap * ✅ latest block on base fixed odos test * ✅ swap for tests + docs * ✅ asserts on swap for tests * ✅ fork tests take current block * ✨ removed all uniswap logic / mentions, renamed odos vars, updated docs * 👷 replaced ternary logic in unwind * ✨ removed unused constants * ✨ fmt * 📚 docs + fmt * ✨ renamed swapFrom test * fmt * ✅ added new bootstrap with current block forking * ✨ fmt * burn test * ✨fmt * 👷 fix: odos unwind swap return (#57) * 👷 fix: odos unwind swap return * fix * ✨ fmt --------- Co-authored-by: Moss * 👷🏻‍♂️ Odos refund + Unwind Test (#58) * 👷 refund logic * ✅ unwind test * 👷 Odos audit fix (#59) * 📚 first round of fixes * 👷 fix 2 * some test fixes * 👷 fix leftovers (#60) * 👷 fix leftovers * fmt --------- Co-authored-by: Moss * audit remidiations 23/11 * 👷 zapOut when unwinding USDC (#64) * 👷 zapOut when unwinding USDC * fix make deploy_base * 👷 adjust minAmountOut to match usdc 6 decimal precision when unwinding udsc * 👷 test fix * ✨ fmt --- .gitmodules | 3 + README.md | 8 +- docs/src/README.md | 8 +- docs/src/src/Zap.sol/contract.Zap.md | 198 +- .../interfaces/IAave.sol/interface.IPool.md | 2 +- .../interfaces/IERC20.sol/interface.IERC20.md | 2 +- .../ISynthetix.sol/interface.IPerpsMarket.md | 2 +- .../ISynthetix.sol/interface.ISpotMarket.md | 2 +- .../IUniswap.sol/interface.IQuoter.md | 2 +- .../IUniswap.sol/interface.IRouter.md | 2 +- .../src/utils/Errors.sol/contract.Errors.md | 10 +- .../src/src/utils/Flush.sol/contract.Flush.md | 2 +- .../Reentrancy.sol/contract.Reentrancy.md | 2 +- .../library.SafeERC20.md | 2 +- foundry.toml | 2 + lib/surl | 1 + makefile | 2 +- script/Deploy.s.sol | 22 +- script/utils/Parameters.sol | 15 +- src/Zap.sol | 227 +- src/interfaces/ISynthetix.sol | 7 + src/utils/Errors.sol | 3 +- src/utils/Flush.sol | 28 +- test/Burn.t.sol | 31 + test/EndToEnd.t.sol | 2008 +++++++++++++++++ test/OdosAPITest.t.sol | 59 + test/Swap.for.t.sol | 174 -- test/Swap.from.t.sol | 136 ++ test/Swap.with.t.sol | 104 - test/Unwind.t.sol | 63 +- test/interfaces/ISynthetix.sol | 33 + test/interfaces/IUniswap.sol | 21 - test/utils/Bootstrap.sol | 134 +- test/utils/BootstrapWithCurrentBlock.sol | 22 + test/utils/Constants.sol | 22 + test/utils/MathLib.sol | 78 + test/utils/OdosSwapData.sol | 169 ++ 37 files changed, 2905 insertions(+), 701 deletions(-) create mode 160000 lib/surl create mode 100644 test/EndToEnd.t.sol create mode 100644 test/OdosAPITest.t.sol delete mode 100644 test/Swap.for.t.sol create mode 100644 test/Swap.from.t.sol delete mode 100644 test/Swap.with.t.sol delete mode 100644 test/interfaces/IUniswap.sol create mode 100644 test/utils/BootstrapWithCurrentBlock.sol create mode 100644 test/utils/MathLib.sol create mode 100644 test/utils/OdosSwapData.sol diff --git a/.gitmodules b/.gitmodules index 888d42d..8a3c514 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/surl"] + path = lib/surl + url = https://github.com/memester-xyz/surl diff --git a/README.md b/README.md index 645e85d..278e995 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ## Overview -Zap is a smart contract that facilitates stablecoin "zapping" and Synthetix native collateral unwinding by integrating flash loans from Aave and Uniswap v3 swaps. +Zap is a smart contract that facilitates stablecoin "zapping" and Synthetix native collateral unwinding by integrating flash loans from Aave and Odos swaps. ### What is a **Zap**? @@ -29,7 +29,7 @@ USDC <--(spot market)--> sUSDC <--(spot market)--> USDx 2. **Zap into USDx (Synthetix Spot Market):** Use the borrowed funds to zap into USDx. 3. **Burn USDx & Repay Debt (Synthetix Core):** Repay Synthetix debt by burning USDx. 4. **Withdraw and Unwrap Collateral (Synthetix Spot Market):** Withdraw margin (e.g., sETH) and convert it back to underlying assets (e.g., WETH). -5. **Swap (Uniswap):** Exchange collateral assets (like WETH) for USDC to repay the flash loan. +5. **Swap (Odos):** Exchange collateral assets (like WETH) for USDC to repay the flash loan. 6. **Flash Loan Repayment (Aave):** The USDC loan, including the premium, is repaid to Aave. 7. **Send Remaining Collateral (Synthetix):** Any surplus collateral is returned to the user. @@ -38,10 +38,10 @@ USDC <--(spot market)--> sUSDC <--(spot market)--> USDx - Zap via Synthetix - Wrap & Unwrap Collateral via Synthetix - Buy & Sell via Synthetix -- Unwind Collateral via Synthetix, Aave, and Uniswap +- Unwind Collateral via Synthetix, Aave, and Odos - Burn Debt via Synthetix - Withdraw Perp Collateral via Synthetix -- Swap via Uniswap +- Swap via Odos ## Development diff --git a/docs/src/README.md b/docs/src/README.md index 645e85d..278e995 100644 --- a/docs/src/README.md +++ b/docs/src/README.md @@ -13,7 +13,7 @@ ## Overview -Zap is a smart contract that facilitates stablecoin "zapping" and Synthetix native collateral unwinding by integrating flash loans from Aave and Uniswap v3 swaps. +Zap is a smart contract that facilitates stablecoin "zapping" and Synthetix native collateral unwinding by integrating flash loans from Aave and Odos swaps. ### What is a **Zap**? @@ -29,7 +29,7 @@ USDC <--(spot market)--> sUSDC <--(spot market)--> USDx 2. **Zap into USDx (Synthetix Spot Market):** Use the borrowed funds to zap into USDx. 3. **Burn USDx & Repay Debt (Synthetix Core):** Repay Synthetix debt by burning USDx. 4. **Withdraw and Unwrap Collateral (Synthetix Spot Market):** Withdraw margin (e.g., sETH) and convert it back to underlying assets (e.g., WETH). -5. **Swap (Uniswap):** Exchange collateral assets (like WETH) for USDC to repay the flash loan. +5. **Swap (Odos):** Exchange collateral assets (like WETH) for USDC to repay the flash loan. 6. **Flash Loan Repayment (Aave):** The USDC loan, including the premium, is repaid to Aave. 7. **Send Remaining Collateral (Synthetix):** Any surplus collateral is returned to the user. @@ -38,10 +38,10 @@ USDC <--(spot market)--> sUSDC <--(spot market)--> USDx - Zap via Synthetix - Wrap & Unwrap Collateral via Synthetix - Buy & Sell via Synthetix -- Unwind Collateral via Synthetix, Aave, and Uniswap +- Unwind Collateral via Synthetix, Aave, and Odos - Burn Debt via Synthetix - Withdraw Perp Collateral via Synthetix -- Swap via Uniswap +- Swap via Odos ## Development diff --git a/docs/src/src/Zap.sol/contract.Zap.md b/docs/src/src/Zap.sol/contract.Zap.md index f42277a..6672cc5 100644 --- a/docs/src/src/Zap.sol/contract.Zap.md +++ b/docs/src/src/Zap.sol/contract.Zap.md @@ -1,5 +1,5 @@ # Zap -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/Zap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/Zap.sol) **Inherits:** [Reentrancy](/src/utils/Reentrancy.sol/contract.Reentrancy.md), [Errors](/src/utils/Errors.sol/contract.Errors.md), [Flush](/src/utils/Flush.sol/contract.Flush.md) @@ -90,13 +90,6 @@ address public immutable AAVE; ``` -### FEE_TIER - -```solidity -uint24 public constant FEE_TIER = 3000; -``` - - ### ROUTER ```solidity @@ -104,13 +97,6 @@ address public immutable ROUTER; ``` -### QUOTER - -```solidity -address public immutable QUOTER; -``` - - ## Functions ### constructor @@ -124,8 +110,7 @@ constructor( address _referrer, uint128 _susdcSpotId, address _aave, - address _router, - address _quoter + address _router ); ``` @@ -469,7 +454,7 @@ function unwind( bytes memory _path, uint256 _zapMinAmountOut, uint256 _unwrapMinAmountOut, - uint256 _swapMaxAmountIn, + uint256 _swapAmountIn, address _receiver ) external @@ -484,10 +469,10 @@ function unwind( |`_collateralId`|`uint128`|synthetix market id of collateral| |`_collateralAmount`|`uint256`|amount of collateral to unwind| |`_collateral`|`address`|address of collateral to unwind| -|`_path`|`bytes`|Uniswap swap path encoded in reverse order| +|`_path`|`bytes`|odos path from the sor/assemble api endpoint| |`_zapMinAmountOut`|`uint256`|acceptable slippage for zapping| |`_unwrapMinAmountOut`|`uint256`|acceptable slippage for unwrapping| -|`_swapMaxAmountIn`|`uint256`|acceptable slippage for swapping| +|`_swapAmountIn`|`uint256`|acceptable slippage for swapping| |`_receiver`|`address`|address to receive unwound collateral| @@ -683,200 +668,65 @@ function _withdraw( internal; ``` -### quoteSwapFor - -query amount required to receive a specific amount of token - -*this is the QuoterV1 interface* - -*_path MUST be encoded backwards for `exactOutput`* - -*quoting is NOT gas efficient and should NOT be called on chain* - - -```solidity -function quoteSwapFor( - bytes memory _path, - uint256 _amountOut -) - external - returns ( - uint256 amountIn, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList, - uint256 gasEstimate - ); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`_path`|`bytes`|Uniswap swap path encoded in reverse order| -|`_amountOut`|`uint256`|is the desired output amount| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`amountIn`|`uint256`|required as the input for the swap in order| -|`sqrtPriceX96AfterList`|`uint160[]`|| -|`initializedTicksCrossedList`|`uint32[]`|| -|`gasEstimate`|`uint256`|| - - -### quoteSwapWith - -query amount received for a specific amount of token to spend - -*this is the QuoterV1 interface* - -*_path MUST be encoded in order for `exactInput`* - -*quoting is NOT gas efficient and should NOT be called on chain* - - -```solidity -function quoteSwapWith( - bytes memory _path, - uint256 _amountIn -) - external - returns ( - uint256 amountOut, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList, - uint256 gasEstimate - ); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`_path`|`bytes`|Uniswap swap path encoded in order| -|`_amountIn`|`uint256`|is the input amount to spendp| +### swapFrom -**Returns** +swap an amount of tokens for the optimal amount of USDC -|Name|Type|Description| -|----|----|-----------| -|`amountOut`|`uint256`|received as the output for the swap in order| -|`sqrtPriceX96AfterList`|`uint160[]`|| -|`initializedTicksCrossedList`|`uint32[]`|| -|`gasEstimate`|`uint256`|| - - -### swapFor - -swap a tolerable amount of tokens for a specific amount of USDC - -*_path MUST be encoded backwards for `exactOutput`* +*_path USDC is not enforced as the output token during the swap, but +is the expected in the call to push* *caller must grant token allowance to this contract* -*any excess token not spent will be returned to the caller* - ```solidity -function swapFor( +function swapFrom( address _from, bytes memory _path, - uint256 _amount, - uint256 _maxAmountIn, + uint256 _amountIn, address _receiver ) external - returns (uint256 deducted); + returns (uint256 amountOut); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`_from`|`address`|address of token to swap| -|`_path`|`bytes`|uniswap swap path encoded in reverse order| -|`_amount`|`uint256`|amount of USDC to receive in return| -|`_maxAmountIn`|`uint256`|max amount of token to spend| +|`_path`|`bytes`|odos path from the sor/assemble api endpoint| +|`_amountIn`|`uint256`|amount of token to spend| |`_receiver`|`address`|address to receive USDC| **Returns** |Name|Type|Description| |----|----|-----------| -|`deducted`|`uint256`|amount of incoming token; i.e., amount spent| +|`amountOut`|`uint256`|amount of tokens swapped for| -### _swapFor - -*allowance is assumed* +### odosSwap *following execution, this contract will hold the swapped USDC* ```solidity -function _swapFor( - address _from, - bytes memory _path, - uint256 _amount, - uint256 _maxAmountIn +function odosSwap( + address _tokenFrom, + uint256 _amountIn, + bytes memory _swapPath ) internal - returns (uint256 deducted); -``` - -### swapWith - -swap a specific amount of tokens for a tolerable amount of USDC - -*_path MUST be encoded in order for `exactInput`* - -*caller must grant token allowance to this contract* - - -```solidity -function swapWith( - address _from, - bytes memory _path, - uint256 _amount, - uint256 _amountOutMinimum, - address _receiver -) - external - returns (uint256 received); + returns (uint256 amountOut); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`_from`|`address`|address of token to swap| -|`_path`|`bytes`|uniswap swap path encoded in order| -|`_amount`|`uint256`|of token to swap| -|`_amountOutMinimum`|`uint256`|tolerable amount of USDC to receive specified with 6 decimals| -|`_receiver`|`address`|address to receive USDC| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`received`|`uint256`|amount of USDC| - - -### _swapWith - -*allowance is assumed* - -*following execution, this contract will hold the swapped USDC* +|`_tokenFrom`|`address`|address of token being swapped| +|`_amountIn`|`uint256`|amount of token being swapped| +|`_swapPath`|`bytes`|bytes from odos assemble api containing the swap details| -```solidity -function _swapWith( - address _from, - bytes memory _path, - uint256 _amount, - uint256 _amountOutMinimum -) - internal - returns (uint256 received); -``` - ### _pull *pull tokens from a sender* diff --git a/docs/src/src/interfaces/IAave.sol/interface.IPool.md b/docs/src/src/interfaces/IAave.sol/interface.IPool.md index 61a1769..7aa3f02 100644 --- a/docs/src/src/interfaces/IAave.sol/interface.IPool.md +++ b/docs/src/src/interfaces/IAave.sol/interface.IPool.md @@ -1,5 +1,5 @@ # IPool -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/IAave.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/interfaces/IAave.sol) ## Functions diff --git a/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md b/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md index f77b786..cce5ab6 100644 --- a/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md +++ b/docs/src/src/interfaces/IERC20.sol/interface.IERC20.md @@ -1,5 +1,5 @@ # IERC20 -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/IERC20.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/interfaces/IERC20.sol) ## Functions diff --git a/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md b/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md index 0249c37..d8b61de 100644 --- a/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md +++ b/docs/src/src/interfaces/ISynthetix.sol/interface.IPerpsMarket.md @@ -1,5 +1,5 @@ # IPerpsMarket -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/ISynthetix.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/interfaces/ISynthetix.sol) ## Functions diff --git a/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md b/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md index af986f1..5c16970 100644 --- a/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md +++ b/docs/src/src/interfaces/ISynthetix.sol/interface.ISpotMarket.md @@ -1,5 +1,5 @@ # ISpotMarket -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/ISynthetix.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/interfaces/ISynthetix.sol) ## Functions diff --git a/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md b/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md index cfdca75..a0b258f 100644 --- a/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md +++ b/docs/src/src/interfaces/IUniswap.sol/interface.IQuoter.md @@ -1,5 +1,5 @@ # IQuoter -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/IUniswap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/interfaces/IUniswap.sol) ## Functions diff --git a/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md b/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md index 75b817e..b8f3f9c 100644 --- a/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md +++ b/docs/src/src/interfaces/IUniswap.sol/interface.IRouter.md @@ -1,5 +1,5 @@ # IRouter -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/interfaces/IUniswap.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/interfaces/IUniswap.sol) ## Functions diff --git a/docs/src/src/utils/Errors.sol/contract.Errors.md b/docs/src/src/utils/Errors.sol/contract.Errors.md index 52093c8..b4d46cf 100644 --- a/docs/src/src/utils/Errors.sol/contract.Errors.md +++ b/docs/src/src/utils/Errors.sol/contract.Errors.md @@ -1,5 +1,5 @@ # Errors -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/utils/Errors.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/utils/Errors.sol) **Author:** @jaredborders @@ -67,15 +67,9 @@ thrown when a swap operation fails ```solidity -error SwapFailed(string reason); +error SwapFailed(); ``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`reason`|`string`|string for the failure| - ### NotPermitted thrown when operation is not permitted diff --git a/docs/src/src/utils/Flush.sol/contract.Flush.md b/docs/src/src/utils/Flush.sol/contract.Flush.md index 96524b4..62cd9a8 100644 --- a/docs/src/src/utils/Flush.sol/contract.Flush.md +++ b/docs/src/src/utils/Flush.sol/contract.Flush.md @@ -1,5 +1,5 @@ # Flush -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/utils/Flush.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/utils/Flush.sol) **Author:** @jaredborders diff --git a/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md b/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md index a4c30b0..b684527 100644 --- a/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md +++ b/docs/src/src/utils/Reentrancy.sol/contract.Reentrancy.md @@ -1,5 +1,5 @@ # Reentrancy -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/utils/Reentrancy.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/utils/Reentrancy.sol) **Authors:** @moss-eth, @jaredborders diff --git a/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md b/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md index 48dfd16..fc0eaf6 100644 --- a/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md +++ b/docs/src/src/utils/SafeTransferERC20.sol/library.SafeERC20.md @@ -1,5 +1,5 @@ # SafeERC20 -[Git Source](https://github.com/moss-eth/zap/blob/70d3ea131ffe8af2f978b53f91daa0d8ac74d19a/src/utils/SafeTransferERC20.sol) +[Git Source](https://github.com/moss-eth/zap/blob/59cf0756a77f382e301eda36c7e1793c595fd9b7/src/utils/SafeTransferERC20.sol) *Wrappers around ERC-20 operations that throw on failure (when the token contract returns false). Tokens that return no value (and instead revert or diff --git a/foundry.toml b/foundry.toml index 2403b24..06604a9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,6 +7,8 @@ solc_version = "0.8.27" optimizer = true optimizer_runs = 500_000 via-ir = true +ffi = true +evm_version = "cancun" [fmt] line_length = 80 diff --git a/lib/surl b/lib/surl new file mode 160000 index 0000000..034c912 --- /dev/null +++ b/lib/surl @@ -0,0 +1 @@ +Subproject commit 034c912ae9b5e707a5afd21f145b452ad8e800df diff --git a/makefile b/makefile index 7909907..602be6d 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,5 @@ deploy_base: - source .env && forge script --chain base script/Deploy.s.sol:DeployArbitrum --rpc-url $$BASE_RPC --broadcast --verify -vvvv + source .env && forge script --chain base script/Deploy.s.sol:DeployBase --rpc-url $$BASE_RPC --broadcast --verify -vvvv deploy_arbitrum: source .env && forge script --chain arbitrum script/Deploy.s.sol:DeployArbitrum --rpc-url $$ARBITRUM_RPC --broadcast --verify -vvvv diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 038fc27..636435b 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -25,8 +25,7 @@ contract Deploy is Script { address referrer, uint128 susdcSpotId, address aave, - address router, - address quoter + address router ) public returns (Zap zap) @@ -39,8 +38,7 @@ contract Deploy is Script { _referrer: referrer, _susdcSpotId: susdcSpotId, _aave: aave, - _router: router, - _quoter: quoter + _router: router }); } @@ -58,10 +56,10 @@ contract DeployBase is Deploy, Base { referrer: BASE_REFERRER, susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, aave: BASE_AAVE_POOL, - router: BASE_ROUTER, - quoter: BASE_QUOTER + router: BASE_ROUTER }); - Flush(address(zap)).designatePlumber(BASE_PDAO); + // PDAO will have to accept Nomination + Flush(address(zap)).nominatePlumber(BASE_PDAO); } } @@ -78,10 +76,9 @@ contract DeployArbitrum is Deploy, Arbitrum { referrer: ARBITRUM_REFERRER, susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, aave: ARBITRUM_AAVE_POOL, - router: ARBITRUM_ROUTER, - quoter: ARBITRUM_QUOTER + router: ARBITRUM_ROUTER }); - Flush(address(zap)).designatePlumber(ARBITRUM_PDAO); + Flush(address(zap)).nominatePlumber(ARBITRUM_PDAO); } } @@ -98,10 +95,9 @@ contract DeployArbitrumSepolia is Deploy, ArbitrumSepolia { referrer: ARBITRUM_SEPOLIA_REFERRER, susdcSpotId: ARBITRUM_SEPOLIA_SUSDC_SPOT_MARKET_ID, aave: ARBITRUM_SEPOLIA_AAVE_POOL, - router: ARBITRUM_SEPOLIA_ROUTER, - quoter: ARBITRUM_SEPOLIA_QUOTER + router: ARBITRUM_SEPOLIA_ROUTER }); - Flush(address(zap)).designatePlumber(ARBITRUM_SEPOLIA_PDAO); + Flush(address(zap)).nominatePlumber(ARBITRUM_SEPOLIA_PDAO); } } diff --git a/script/utils/Parameters.sol b/script/utils/Parameters.sol index f63a677..c17f72d 100644 --- a/script/utils/Parameters.sol +++ b/script/utils/Parameters.sol @@ -17,9 +17,8 @@ contract Base { /// @custom:aave address BASE_AAVE_POOL = 0xA238Dd80C259a72e81d7e4664a9801593F98d1c5; - /// @custom:uniswap - address BASE_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481; - address BASE_QUOTER = 0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a; + /// @custom:odos + address BASE_ROUTER = 0x19cEeAd7105607Cd444F5ad10dd51356436095a1; } @@ -39,9 +38,8 @@ contract Arbitrum { /// @custom:aave address ARBITRUM_AAVE_POOL = 0x794a61358D6845594F94dc1DB02A252b5b4814aD; - /// @custom:uniswap - address ARBITRUM_ROUTER = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; - address ARBITRUM_QUOTER = 0x61fFE014bA17989E743c5F6cB21bF9697530B21e; + /// @custom:odos + address ARBITRUM_ROUTER = 0xa669e7A0d4b3e4Fa48af2dE86BD4CD7126Be4e13; } @@ -64,8 +62,7 @@ contract ArbitrumSepolia { address ARBITRUM_SEPOLIA_AAVE_POOL = 0xBfC91D59fdAA134A4ED45f7B584cAf96D7792Eff; - /// @custom:uniswap - address ARBITRUM_SEPOLIA_ROUTER = 0x101F443B4d1b059569D643917553c771E1b9663E; - address ARBITRUM_SEPOLIA_QUOTER = 0x2779a0CC1c3e0E44D2542EC3e79e3864Ae93Ef0B; + /// @custom:odos + address ARBITRUM_SEPOLIA_ROUTER = address(0); } diff --git a/src/Zap.sol b/src/Zap.sol index 538b7fb..4d97381 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.27; import {IPool} from "./interfaces/IAave.sol"; import {IERC20} from "./interfaces/IERC20.sol"; import {IPerpsMarket, ISpotMarket} from "./interfaces/ISynthetix.sol"; -import {IQuoter, IRouter} from "./interfaces/IUniswap.sol"; import {Errors} from "./utils/Errors.sol"; import {Flush} from "./utils/Flush.sol"; @@ -14,7 +13,7 @@ import {SafeERC20} from "./utils/SafeTransferERC20.sol"; /// @title zap /// @custom:synthetix zap USDC into and out of USDx /// @custom:aave flash loan USDC to unwind synthetix collateral -/// @custom:uniswap swap unwound collateral for USDC to repay flashloan +/// @custom:odos swap unwound collateral for USDC to repay flashloan /// @dev idle token balances are not safe /// @dev intended for standalone use; do not inherit /// @author @jaredborders @@ -40,10 +39,8 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { uint16 public constant REFERRAL_CODE = 0; address public immutable AAVE; - /// @custom:uniswap - uint24 public constant FEE_TIER = 3000; + /// @custom:odos address public immutable ROUTER; - address public immutable QUOTER; constructor( address _usdc, @@ -53,8 +50,7 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { address _referrer, uint128 _susdcSpotId, address _aave, - address _router, - address _quoter + address _router ) { /// @custom:circle USDC = _usdc; @@ -69,9 +65,8 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { /// @custom:aave AAVE = _aave; - /// @custom:uniswap + /// @custom:odos ROUTER = _router; - QUOTER = _quoter; } /*////////////////////////////////////////////////////////////// @@ -168,7 +163,8 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { /// @notice wrap collateral via synthetix spot market /// @dev caller must grant token allowance to this contract - /// @custom:synth -> synthetix token representation of wrapped collateral + /// @custom:synth -> synthetix token representation of an asset with an + /// acceptable onchain price oracle /// @param _token address of token to wrap /// @param _synthId synthetix market id of synth to wrap into /// @param _amount amount of token to wrap @@ -211,7 +207,8 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { /// @notice unwrap collateral via synthetix spot market /// @dev caller must grant synth allowance to this contract - /// @custom:synth -> synthetix token representation of wrapped collateral + /// @custom:synth -> synthetix token representation of an asset with an + /// acceptable onchain price oracle /// @param _token address of token to unwrap into /// @param _synthId synthetix market id of synth to unwrap /// @param _amount amount of synth to unwrap @@ -345,16 +342,15 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { //////////////////////////////////////////////////////////////*/ /// @notice unwind synthetix perp position collateral - /// @dev caller must grant USDC allowance to this contract /// @custom:synthetix RBAC permission required: "PERPS_MODIFY_COLLATERAL" /// @param _accountId synthetix perp market account id - /// @param _collateralId synthetix market id of collateral + /// @param _collateralId synthetix spot market id or synth id /// @param _collateralAmount amount of collateral to unwind /// @param _collateral address of collateral to unwind - /// @param _path Uniswap swap path encoded in reverse order + /// @param _path odos path from the sor/assemble api endpoint /// @param _zapMinAmountOut acceptable slippage for zapping /// @param _unwrapMinAmountOut acceptable slippage for unwrapping - /// @param _swapMaxAmountIn acceptable slippage for swapping + /// @param _swapAmountIn amount intended to be swapped by odos /// @param _receiver address to receive unwound collateral function unwind( uint128 _accountId, @@ -364,7 +360,7 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { bytes memory _path, uint256 _zapMinAmountOut, uint256 _unwrapMinAmountOut, - uint256 _swapMaxAmountIn, + uint256 _swapAmountIn, address _receiver ) external @@ -381,7 +377,7 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { _path, _zapMinAmountOut, _unwrapMinAmountOut, - _swapMaxAmountIn, + _swapAmountIn, _receiver ); @@ -437,7 +433,7 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { uint256 unwound = _unwind(_flashloan, _premium, _params); - _push(_collateral, _receiver, unwound); + if (unwound > 0) _push(_collateral, _receiver, unwound); return IERC20(USDC).approve(AAVE, _flashloan + _premium); } @@ -464,7 +460,8 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { bytes memory _path, uint256 _zapMinAmountOut, uint256 _unwrapMinAmountOut, - uint256 _swapMaxAmountIn, + uint256 _swapAmountIn, + address _receiver ) = abi.decode( _params, ( @@ -495,9 +492,14 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { // i.e., # of sETH, # of sUSDe, # of sUSDC (...) _withdraw(_collateralId, _collateralAmount, _accountId); - // unwrap withdrawn synthetix perp position collateral; - // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) - unwound = _unwrap(_collateralId, _collateralAmount, _unwrapMinAmountOut); + if (_collateral == USDC) { + unwound = _zapOut(_collateralAmount, _collateralAmount / 1e12); + } else { + // unwrap withdrawn synthetix perp position collateral; + // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) + unwound = + _unwrap(_collateralId, _collateralAmount, _unwrapMinAmountOut); + } // establish total debt now owed to Aave; // i.e., # of USDC @@ -508,9 +510,16 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { // i.e., USDe -(swap)-> USDC -(repay)-> Aave // i.e., USDC -(repay)-> Aave // whatever collateral amount is remaining is returned to the caller - unwound -= _collateral == USDC - ? _flashloan - : _swapFor(_collateral, _path, _flashloan, _swapMaxAmountIn); + if (_collateral == USDC) { + unwound -= _flashloan; + } else { + odosSwap(_collateral, _swapAmountIn, _path); + unwound -= _swapAmountIn; + uint256 leftovers = IERC20(USDC).balanceOf(address(this)); + if (leftovers > _flashloan) { + _push(USDC, _receiver, leftovers - _flashloan); + } + } /// @notice the path and max amount in must take into consideration: /// (1) Aave flashloan amount @@ -629,164 +638,56 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { } /*////////////////////////////////////////////////////////////// - UNISWAP + ODOS //////////////////////////////////////////////////////////////*/ - /// @notice query amount required to receive a specific amount of token - /// @dev this is the QuoterV1 interface - /// @dev _path MUST be encoded backwards for `exactOutput` - /// @dev quoting is NOT gas efficient and should NOT be called on chain - /// @custom:integrator quoting function inclusion is for QoL purposes - /// @param _path Uniswap swap path encoded in reverse order - /// @param _amountOut is the desired output amount - /// @return amountIn required as the input for the swap in order - function quoteSwapFor( - bytes memory _path, - uint256 _amountOut - ) - external - returns ( - uint256 amountIn, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList, - uint256 gasEstimate - ) - { - return IQuoter(QUOTER).quoteExactOutput(_path, _amountOut); - } - - /// @notice query amount received for a specific amount of token to spend - /// @dev this is the QuoterV1 interface - /// @dev _path MUST be encoded in order for `exactInput` - /// @dev quoting is NOT gas efficient and should NOT be called on chain - /// @custom:integrator quoting function inclusion is for QoL purposes - /// @param _path Uniswap swap path encoded in order - /// @param _amountIn is the input amount to spendp - /// @return amountOut received as the output for the swap in order - function quoteSwapWith( - bytes memory _path, - uint256 _amountIn - ) - external - returns ( - uint256 amountOut, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList, - uint256 gasEstimate - ) - { - return IQuoter(QUOTER).quoteExactInput(_path, _amountIn); - } - - /// @notice swap a tolerable amount of tokens for a specific amount of USDC - /// @dev _path MUST be encoded backwards for `exactOutput` + /// @notice swap the input amount of tokens for USDC using Odos + /// @dev _path USDC is not enforced as the output token during the swap, but + /// is expected in the call to push /// @dev caller must grant token allowance to this contract - /// @dev any excess token not spent will be returned to the caller /// @param _from address of token to swap - /// @param _path uniswap swap path encoded in reverse order - /// @param _amount amount of USDC to receive in return - /// @param _maxAmountIn max amount of token to spend + /// @param _path odos path from the sor/assemble api endpoint + /// @param _amountIn amount of token to spend /// @param _receiver address to receive USDC - /// @return deducted amount of incoming token; i.e., amount spent - function swapFor( + /// @return amountOut amount of tokens swapped for + function swapFrom( address _from, bytes memory _path, - uint256 _amount, - uint256 _maxAmountIn, + uint256 _amountIn, address _receiver ) external - returns (uint256 deducted) - { - _pull(_from, msg.sender, _maxAmountIn); - deducted = _swapFor(_from, _path, _amount, _maxAmountIn); - _push(USDC, _receiver, _amount); - - if (deducted < _maxAmountIn) { - _push(_from, msg.sender, _maxAmountIn - deducted); - } - } - - /// @dev allowance is assumed - /// @dev following execution, this contract will hold the swapped USDC - function _swapFor( - address _from, - bytes memory _path, - uint256 _amount, - uint256 _maxAmountIn - ) - internal - returns (uint256 deducted) + returns (uint256 amountOut) { - IERC20(_from).approve(ROUTER, _maxAmountIn); - - IRouter.ExactOutputParams memory params = IRouter.ExactOutputParams({ - path: _path, - recipient: address(this), - amountOut: _amount, - amountInMaximum: _maxAmountIn - }); + _pull(_from, msg.sender, _amountIn); + amountOut = odosSwap(_from, _amountIn, _path); + _push(USDC, _receiver, amountOut); - try IRouter(ROUTER).exactOutput(params) returns (uint256 amountIn) { - deducted = amountIn; - } catch Error(string memory reason) { - revert SwapFailed(reason); - } - - IERC20(_from).approve(ROUTER, 0); - } - - /// @notice swap a specific amount of tokens for a tolerable amount of USDC - /// @dev _path MUST be encoded in order for `exactInput` - /// @dev caller must grant token allowance to this contract - /// @param _from address of token to swap - /// @param _path uniswap swap path encoded in order - /// @param _amount of token to swap - /// @param _amountOutMinimum tolerable amount of USDC to receive specified - /// with 6 - /// decimals - /// @param _receiver address to receive USDC - /// @return received amount of USDC - function swapWith( - address _from, - bytes memory _path, - uint256 _amount, - uint256 _amountOutMinimum, - address _receiver - ) - external - returns (uint256 received) - { - _pull(_from, msg.sender, _amount); - received = _swapWith(_from, _path, _amount, _amountOutMinimum); - _push(USDC, _receiver, received); + // refund if there is any amount of `_from` token left + uint256 amountLeft = IERC20(_from).balanceOf(address(this)); + if (amountLeft > 0) _push(_from, msg.sender, amountLeft); } - /// @dev allowance is assumed /// @dev following execution, this contract will hold the swapped USDC - function _swapWith( - address _from, - bytes memory _path, - uint256 _amount, - uint256 _amountOutMinimum + /// @param _tokenFrom address of token being swapped + /// @param _amountIn amount of token being swapped + /// @param _swapPath bytes from odos assemble api containing the swap + /// details + function odosSwap( + address _tokenFrom, + uint256 _amountIn, + bytes memory _swapPath ) internal - returns (uint256 received) + returns (uint256 amountOut) { - IERC20(_from).approve(ROUTER, _amount); + IERC20(_tokenFrom).approve(ROUTER, _amountIn); - IRouter.ExactInputParams memory params = IRouter.ExactInputParams({ - path: _path, - recipient: address(this), - amountIn: _amount, - amountOutMinimum: _amountOutMinimum - }); + (bool success, bytes memory result) = ROUTER.call{value: 0}(_swapPath); + require(success, SwapFailed()); + amountOut = abi.decode(result, (uint256)); - try IRouter(ROUTER).exactInput(params) returns (uint256 amountOut) { - received = amountOut; - } catch Error(string memory reason) { - revert SwapFailed(reason); - } + IERC20(_tokenFrom).approve(ROUTER, 0); } /*////////////////////////////////////////////////////////////// diff --git a/src/interfaces/ISynthetix.sol b/src/interfaces/ISynthetix.sol index 62d73a4..10681f2 100644 --- a/src/interfaces/ISynthetix.sol +++ b/src/interfaces/ISynthetix.sol @@ -53,6 +53,13 @@ interface ISpotMarket { interface IPerpsMarket { + error InsufficientCollateralAvailableForWithdraw( + int256 withdrawableMarginUsd, uint256 requestedMarginUsd + ); + error PermissionDenied( + uint128 accountId, bytes32 permission, address target + ); + function modifyCollateral( uint128 accountId, uint128 synthMarketId, diff --git a/src/utils/Errors.sol b/src/utils/Errors.sol index 989eef4..56abc41 100644 --- a/src/utils/Errors.sol +++ b/src/utils/Errors.sol @@ -22,8 +22,7 @@ contract Errors { error SellFailed(string reason); /// @notice thrown when a swap operation fails - /// @param reason string for the failure - error SwapFailed(string reason); + error SwapFailed(); /// @notice thrown when operation is not permitted error NotPermitted(); diff --git a/src/utils/Flush.sol b/src/utils/Flush.sol index fa40c28..50cfd96 100644 --- a/src/utils/Flush.sol +++ b/src/utils/Flush.sol @@ -10,13 +10,22 @@ contract Flush { /// @custom:plumber address public PLUMBER; + address public nominatedPlumber; + /// @notice thrown when caller is not the plumber error OnlyPlumber(); - ///@notice emitted when a new plumber is designated - event PlumberDesignated(address plumber); + /// @notice thrown when caller is not nominated to be plumber + error OnlyNominatedPlumber(); + + /// @notice emitted when a new plumber is nominated + event PlumberNominated(address plumber); + + ///@notice emitted when a new plumber accepts nomination + event PlumberNominationAccepted(address plumber); constructor(address _plumber) { + require(_plumber != address(0)); PLUMBER = _plumber; } @@ -30,14 +39,21 @@ contract Flush { if (balance > 0) token.transfer(msg.sender, balance); } - /// @notice designate a new plumber + /// @notice nominate a new plumber /// @custom:plumber is the only authorized caller /// @dev zero address can be used to remove flush capability /// @param _newPlumber address of new plumber - function designatePlumber(address _newPlumber) external { + function nominatePlumber(address _newPlumber) external { require(msg.sender == PLUMBER, OnlyPlumber()); - PLUMBER = _newPlumber; - emit PlumberDesignated(_newPlumber); + nominatedPlumber = _newPlumber; + emit PlumberNominated(_newPlumber); + } + + function acceptPlumberNomination() external { + require(msg.sender == nominatedPlumber, OnlyNominatedPlumber()); + PLUMBER = nominatedPlumber; + nominatedPlumber = address(0); + emit PlumberNominationAccepted(PLUMBER); } } diff --git a/test/Burn.t.sol b/test/Burn.t.sol index d11021e..8ea5e5c 100644 --- a/test/Burn.t.sol +++ b/test/Burn.t.sol @@ -12,8 +12,39 @@ import { Zap } from "./utils/Bootstrap.sol"; +import "forge-std/console2.sol"; + contract BurnTest is Bootstrap { + /// @custom:todo + ///@notice passes at block 269_610_923 + function test_burn_arbitrum(uint32 amount) public arbitrum { + IERC20 A_USDX = IERC20(ARBITRUM_USDX); + uint128 accountID = 170_141_183_460_469_231_731_687_303_715_884_105_766; + + address accountOwner = 0x12a41a75793b6ac2cdDAF680798BB461a1024a46; + + uint256 debt = IPerpsMarket(zap.PERPS_MARKET()).debt(accountID); + + vm.assume(amount > 1e6 && amount <= debt); + + uint256 balBefore = A_USDX.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + A_USDX.approve(address(zap), type(uint256).max); + + zap.burn(amount, accountID); + + uint256 balAfter = A_USDX.balanceOf(accountOwner); + uint256 debtAfter = IPerpsMarket(zap.PERPS_MARKET()).debt(accountID); + + assertEq(balAfter, balBefore - amount); + assertEq(debt - amount, debtAfter); + } + + /// @custom:todo + function test_burn_base(uint32 amount) public base {} + /// @custom:todo function test_burn_arbitrum_sepolia(uint32 amount) public diff --git a/test/EndToEnd.t.sol b/test/EndToEnd.t.sol new file mode 100644 index 0000000..09cdec4 --- /dev/null +++ b/test/EndToEnd.t.sol @@ -0,0 +1,2008 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Arbitrum} from "../script/utils/Parameters.sol"; +import {IERC20, Zap} from "../src/Zap.sol"; +import {Flush} from "../src/utils/Flush.sol"; +import "./interfaces/ISynthetix.sol"; + +import {Bootstrap} from "./utils/Bootstrap.sol"; +import {Constants} from "./utils/Constants.sol"; +import {OdosSwapData} from "./utils/OdosSwapData.sol"; +import "forge-std/Test.sol"; + +interface IPyth { + + struct PythPrice { + // Price + int64 price; + // Confidence interval around the price + uint64 conf; + // Price exponent + int32 expo; + // Unix timestamp describing when the price was published + uint256 publishTime; + } + + function getPriceUnsafe(bytes32 id) + external + view + returns (PythPrice memory price); + +} + +contract Attacker { + + IERC20 usdc; + address zap; + + constructor(IERC20 _usdc, address _zap) { + usdc = _usdc; + zap = _zap; + } + + function sweep(IERC20 token) public { + token.transfer(msg.sender, token.balanceOf(address(this))); + } + + receive() external payable { + // could call back into zap to do another swap but that just pays more + // fees, no real profit + // could transfer usdc into zap for zap to be able to pay back the + // original caller but that is also just shuffling your own assets + // around + // cant call reenter into executeOperation because of access controls + } + +} + +contract EndToEndTest is Test, Arbitrum, Constants, OdosSwapData { + + Zap zap; + + ISpotMarket spotMarket; + IPerpsMarket perpsMarket; + + IERC20 usdc; + IERC20 susdc; + IERC20 usdx; + IERC20 weth; + + uint128 smallAccountIdNoOi = + 170_141_183_460_469_231_731_687_303_715_884_106_052; + address smallAccountNoOiOwner = 0xbd400F9a17DC18bc031DBF5ffCD2689F4BF650dD; + + uint128 smallAccountIdWithOi = + 170_141_183_460_469_231_731_687_303_715_884_106_146; + address smallAccountWithOiOwner = 0x46232CbDB0512Ca7B00B8271e285BF8447F1330b; + + uint128 largeAccountIdNoOi = + 170_141_183_460_469_231_731_687_303_715_884_105_759; + address largeAccountNoOiOwner = 0x626a7d9f7bBCaEB1Fa88E8128Bec8f2Dd48b2b4d; + + uint128 largeAccountIdWithOi = + 170_141_183_460_469_231_731_687_303_715_884_105_846; + address largeAccountWithOiOwner = 0x1C1e747A6BE850549E9655addf59FD9e7cC2D4dC; + + string RPC = vm.envString("ARBITRUM_RPC"); + mapping(string => uint256) FORK; + + modifier selectFork(uint256 fork) { + initilizeFork(fork); + _; + } + + function setUp() public { + FORK["0p0001_wrong"] = vm.createFork(RPC, blockNumber_0p0001_wrong); + FORK["0p001_wrong"] = vm.createFork(RPC, blockNumber_0p001_wrong); + FORK["0p01_wrong"] = vm.createFork(RPC, blockNumber_0p01_wrong); + FORK["0p1_wrong"] = vm.createFork(RPC, blockNumber_0p1_wrong); + FORK["1_wrong"] = vm.createFork(RPC, blockNumber_1_wrong); + FORK["10_wrong"] = vm.createFork(RPC, blockNumber_10_wrong); + FORK["100_wrong"] = vm.createFork(RPC, blockNumber_100_wrong); + + FORK["0p0001_zap"] = vm.createFork(RPC, blockNumber_0p0001_zap); + FORK["0p001_zap"] = vm.createFork(RPC, blockNumber_0p001_zap); + FORK["0p01_zap"] = vm.createFork(RPC, blockNumber_0p01_zap); + FORK["0p1_zap"] = vm.createFork(RPC, blockNumber_0p1_zap); + FORK["1_zap"] = vm.createFork(RPC, blockNumber_1_zap); + FORK["10_zap"] = vm.createFork(RPC, blockNumber_10_zap); + FORK["100_zap"] = vm.createFork(RPC, blockNumber_100_zap); + + FORK["1000_attacker"] = vm.createFork(RPC, blockNumber_1000_attacker); + } + + function initilizeFork(uint256 fork) public { + vm.selectFork(fork); + + spotMarket = ISpotMarket(ARBITRUM_SPOT_MARKET); + perpsMarket = IPerpsMarket(ARBITRUM_PERPS_MARKET); + + usdc = IERC20(ARBITRUM_USDC); + usdx = IERC20(ARBITRUM_USDX); + weth = IERC20(ARBITRUM_WETH); + + uint128 synthMarketId = ARBITRUM_SUSDC_SPOT_MARKET_ID; + + susdc = IERC20(spotMarket.getSynth(synthMarketId)); + + // create Zap contract to use actual sUSDC synth + zap = new Zap({ + _usdc: address(usdc), + _usdx: address(usdx), + _spotMarket: address(spotMarket), + _perpsMarket: address(perpsMarket), + _referrer: ARBITRUM_REFERRER, + _susdcSpotId: synthMarketId, + _aave: ARBITRUM_AAVE_POOL, + _router: ARBITRUM_ROUTER + }); + + IPyth pyth = IPyth(0xd74CdD8Eef0E97a5a7678F907991316f88E7965A); + + vm.mockCall( + address(pyth), + abi.encodeWithSignature( + "getPriceUnsafe(bytes32)", + 0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a + ), + abi.encode( + 99_990_737, + 105_741, + 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_913_129_639_928, + block.timestamp + ) + ); + vm.mockCall( + address(pyth), + abi.encodeWithSignature( + "getPriceUnsafe(bytes32)", + 0x6ec879b1e9963de5ee97e9c8710b742d6228252a5e2ca12d4ae81d7fe5ee8c5d + ), + abi.encode(99_874_029, 139_807, int32(-8), block.timestamp) + ); + vm.mockCall( + address(pyth), + abi.encodeWithSignature( + "getPriceUnsafe(bytes32)", + 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace + ), + abi.encode(314_393_291_700, 176_780_592, int32(-8), block.timestamp) + ); + vm.mockCall( + address(pyth), + abi.encodeWithSignature( + "getPriceUnsafe(bytes32)", + 0xd69731a2e74ac1ce884fc3890f7ee324b6deb66147055249568869ed700882e4 + ), + abi.encode(191_914, 287, int32(-10), block.timestamp) + ); + vm.mockCall( + address(pyth), + abi.encodeWithSignature( + "getPriceUnsafe(bytes32)", + 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43 + ), + abi.encode( + 9_798_709_797_025, 5_649_852_176, int32(-8), block.timestamp + ) + ); + } + + function _spin(address eoa, IERC20 token, uint256 amount) internal { + vm.assume(amount > 0); + deal(address(token), eoa, amount); + } + + // this test fails because unspent input asses it not refunded + function testOdosSwapSmall_Success() + public + selectFork(FORK["0p0001_zap"]) + { + address user = vm.addr(1); + uint256 amount = 1e18; + _spin(user, weth, amount); + + uint256 wethBefore = weth.balanceOf(address(user)); + uint256 usdcBefore = usdc.balanceOf(address(user)); + + vm.startPrank(user); + weth.approve(address(zap), amount); + zap.swapFrom(address(weth), swapData_0p0001_zap, amount, user); + vm.stopPrank(); + + assertEq(weth.balanceOf(address(user)), wethBefore - 0.0001 ether); + assertGt( + usdc.balanceOf(address(user)), + usdcBefore + ((1000 - 35) * outAmount_0p0001_zap / 1000) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testOdosSwapMedium_Success() public selectFork(FORK["1_zap"]) { + address user = vm.addr(1); + uint256 amount = 1e18; + _spin(user, weth, amount); + + uint256 wethBefore = weth.balanceOf(address(user)); + uint256 usdcBefore = usdc.balanceOf(address(user)); + + vm.startPrank(user); + weth.approve(address(zap), amount); + zap.swapFrom(address(weth), swapData_1_zap, amount, user); + vm.stopPrank(); + + assertEq(weth.balanceOf(address(user)), wethBefore - 1 ether); + assertGt( + usdc.balanceOf(address(user)), + usdcBefore + ((1000 - 35) * outAmount_1_zap / 1000) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testOdosSwapLarge_Success() public selectFork(FORK["10_zap"]) { + address user = vm.addr(1); + uint256 amount = 120e18; + _spin(user, weth, amount); + + uint256 wethBefore = weth.balanceOf(address(user)); + uint256 usdcBefore = usdc.balanceOf(address(user)); + + vm.startPrank(user); + weth.approve(address(zap), 100 ether); + zap.swapFrom(address(weth), swapData_10_zap, 10 ether, user); + vm.stopPrank(); + + assertEq(weth.balanceOf(address(user)), wethBefore - 10 ether); + assertGt( + usdc.balanceOf(address(user)), + usdcBefore + ((1000 - 35) * outAmount_10_zap / 1000) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + // this test fails because unspent input asset is not refunded + function testOdosSwapRepeat_Success() public selectFork(FORK["1_zap"]) { + // odos swap data can be repeated! + address user = vm.addr(1); + uint256 amount = 1e18; + _spin(user, weth, amount * 2); + + uint256 wethBefore = weth.balanceOf(address(user)); + uint256 usdcBefore = usdc.balanceOf(address(user)); + + vm.startPrank(user); + weth.approve(address(zap), amount); + zap.swapFrom(address(weth), swapData_1_zap, amount, user); + vm.stopPrank(); + + assertEq(weth.balanceOf(address(user)), wethBefore - 1 ether); + assertGe( + usdc.balanceOf(address(user)), + usdcBefore + ((1000 - 10) * outAmount_1_zap / 1000) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + + vm.startPrank(user); + weth.approve(address(zap), amount); + zap.swapFrom(address(weth), swapData_1_zap, amount, user); + vm.stopPrank(); + + assertEq(weth.balanceOf(address(user)), wethBefore - 2 ether); + assertGe( + usdc.balanceOf(address(user)), + usdcBefore + 2 * ((1000 - 10) * outAmount_1_zap / 1000) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testOdosSwapSendToWrongAddress_Fail() + public + selectFork(FORK["1_wrong"]) + { + // failing because slippage exceeded + address user = vm.addr(1); + uint256 amount = 1e18; + _spin(user, weth, amount); + + uint256 wethBefore = weth.balanceOf(address(user)); + uint256 usdcBefore = usdc.balanceOf(address(user)); + + vm.startPrank(user); + weth.approve(address(zap), amount); + + vm.expectRevert(bytes("ERC20: transfer amount exceeds balance")); + zap.swapFrom(address(weth), swapData_1_wrong, amount, user); + vm.stopPrank(); + + assertEq(weth.balanceOf(address(user)), wethBefore); + assertEq(usdc.balanceOf(address(user)), usdcBefore); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testOdosSwapSendToContract_Reentrancy_Success() + public + selectFork(FORK["10_attacker"]) + { + Attacker attackerContract = new Attacker(usdc, address(zap)); + uint256 amountUsdc = 1 ether; // non-realistic amount of USDC, but it is + // more to prove that the reentrancy is possible but not feasible or + // exploitable + address attacker = vm.addr(1234); + + _spin(attacker, usdc, amountUsdc); + + uint256 usdcBalBefore = usdc.balanceOf(attacker); + uint256 ethBalBefore = address(attackerContract).balance; + assertEq(ethBalBefore, 0); + assertEq(usdcBalBefore, amountUsdc); + + vm.startPrank(attacker); + usdc.approve(address(zap), amountUsdc); + zap.swapFrom(address(usdc), swapData_10_attacker, amountUsdc, attacker); + vm.stopPrank(); + + uint256 ethBalAfter = address(attackerContract).balance; + uint256 usdcBalAfter = usdc.balanceOf(attacker); + + assertGe( + ethBalAfter, + ethBalBefore + (1000 - 10) * outAmount_10_attacker / 1000 + ); + + uint256 unrefundedUsdcZap = usdc.balanceOf(address(zap)); + assertEq(unrefundedUsdcZap, 0); + // the final amounts are less than or equal to the amount provided + // initially, ie no profit + assertEq(usdcBalBefore, usdcBalAfter + inAmount_10_attacker); + } + + function testUnwindSmallDebtNoOI_Overpay_AccountOwner_Success() + public + selectFork(FORK["0p01_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address accountOwner = smallAccountNoOiOwner; + + uint256 tcvBefore = perpsMarket.totalCollateralValue(accountId); + uint256 debt = perpsMarket.debt(accountId); + uint256 collateralAmount = perpsMarket.getCollateralAmount(accountId, 4); + uint256 wethBefore = weth.balanceOf(address(this)); + uint256 usdcBefore = usdc.balanceOf(address(this)); + + assertGt(tcvBefore, 0); + assertGt(debt, 0); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + + vm.startPrank(accountOwner); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + + zap.unwind( + accountId, + 4, // sETH collateral ID + collateralAmount, + address(weth), + swapData_0p01_zap, + 0, + 0, + 0.01 ether, + address(this) + ); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(perpsMarket.totalCollateralValue(accountId), 0); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + assertFalse( + perpsMarket.hasPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + + assertEq( + weth.balanceOf(address(this)), + wethBefore + collateralAmount - 0.01 ether + ); + assertGt( + usdc.balanceOf(address(this)), + usdcBefore + ((1000 - 35) * outAmount_0p01_zap / 1000) + - (debt / 1e12) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertLt(usdx.balanceOf(address(zap)), 1 ether); + } + + function testUnwindSmallDebtWithOI_OverPay_AccountOwner_Success() + public + selectFork(FORK["0p01_zap"]) + { + uint128 accountId = smallAccountIdWithOi; + address accountOwner = smallAccountWithOiOwner; + + uint256 tcvBefore = perpsMarket.totalCollateralValue(accountId); + uint256 debt = perpsMarket.debt(accountId); + uint256 collateralAmount = perpsMarket.getCollateralAmount(accountId, 4); + uint256 wethBefore = weth.balanceOf(address(this)); + uint256 usdcBefore = usdc.balanceOf(address(this)); + + assertGt(tcvBefore, 0); + assertGt(debt, 0); + assertGt(perpsMarket.totalAccountOpenInterest(accountId), 0); + + vm.startPrank(accountOwner); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + + zap.unwind( + accountId, + 4, // sETH collateral ID + 0.01 ether, + address(weth), + swapData_0p01_zap, + 0, + 0, + 0.01 ether, + address(this) + ); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq( + perpsMarket.getCollateralAmount(accountId, 4), + collateralAmount - 0.01 ether + ); + assertGt( + perpsMarket.totalCollateralValue(accountId), + tcvBefore - outValue_0p01_zap + ); // this check is weak because the oracle price on this block is diff + // from the odos api price + assertGt(perpsMarket.totalAccountOpenInterest(accountId), 0); + assertFalse( + perpsMarket.hasPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + + assertEq(weth.balanceOf(address(this)), wethBefore); + assertGt( + usdc.balanceOf(address(this)), + usdcBefore + ((1000 - 35) * outAmount_0p01_zap / 1000) + - (debt / 1e12) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertLt(usdx.balanceOf(address(zap)), 1 ether); + } + + function testUnwindSmallDebtNoOI_Underpay_AccountOwner_Fail() + public + selectFork(FORK["0p001_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address accountOwner = smallAccountNoOiOwner; + + uint256 tcvBefore = perpsMarket.totalCollateralValue(accountId); + uint256 debt = perpsMarket.debt(accountId); + uint256 collateralAmount = perpsMarket.getCollateralAmount(accountId, 4); + uint256 wethBefore = weth.balanceOf(address(this)); + uint256 usdcBefore = usdc.balanceOf(address(this)); + + assertGt(tcvBefore, 0); + assertGt(debt, 0); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + + vm.startPrank(accountOwner); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + + vm.expectRevert(bytes("ERC20: transfer amount exceeds balance")); + zap.unwind( + accountId, + 4, // sETH collateral ID + collateralAmount, + address(weth), + swapData_0p001_zap, + 0, + 0, + 0.001 ether, + address(this) + ); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt); + assertEq(perpsMarket.totalCollateralValue(accountId), tcvBefore); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + assertTrue( + perpsMarket.hasPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + + assertEq(weth.balanceOf(address(this)), wethBefore); + assertEq(usdc.balanceOf(address(this)), usdcBefore); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testUnwindSmallDebtNoOI_Overpay_AccountOwner_OdosToWrongAddress_Fail( + ) + public + selectFork(FORK["0p01_wrong"]) + { + uint128 accountId = smallAccountIdNoOi; + address accountOwner = smallAccountNoOiOwner; + + uint256 tcvBefore = perpsMarket.totalCollateralValue(accountId); + uint256 debt = perpsMarket.debt(accountId); + uint256 collateralAmount = perpsMarket.getCollateralAmount(accountId, 4); + uint256 wethBefore = weth.balanceOf(address(this)); + uint256 usdcBefore = usdc.balanceOf(address(this)); + + assertGt(tcvBefore, 0); + assertGt(debt, 0); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + + vm.startPrank(accountOwner); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + + vm.expectRevert(bytes("ERC20: transfer amount exceeds balance")); + zap.unwind( + accountId, + 4, // sETH collateral ID + collateralAmount, + address(weth), + swapData_0p01_wrong, + 0, + 0, + 0.01 ether, + address(this) + ); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt); + assertEq(perpsMarket.totalCollateralValue(accountId), tcvBefore); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + assertTrue( + perpsMarket.hasPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + + assertEq(weth.balanceOf(address(this)), wethBefore); + assertEq(usdc.balanceOf(address(this)), usdcBefore); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + error NotPermitted(); + + function testUnwindSmallDebtNoOI_Overpay_NotAccountOwner_Fail() + public + selectFork(FORK["0p01_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address accountOwner = smallAccountNoOiOwner; + + uint256 tcvBefore = perpsMarket.totalCollateralValue(accountId); + uint256 debt = perpsMarket.debt(accountId); + uint256 collateralAmount = perpsMarket.getCollateralAmount(accountId, 4); + uint256 wethBefore = weth.balanceOf(address(this)); + uint256 usdcBefore = usdc.balanceOf(address(this)); + + assertGt(tcvBefore, 0); + assertGt(debt, 0); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + + vm.startPrank(accountOwner); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + vm.stopPrank(); + + vm.expectRevert(abi.encodeWithSelector(NotPermitted.selector)); + zap.unwind( + accountId, + 4, + collateralAmount, + address(weth), + swapData_0p001_zap, + 0, + 0, + 0.001 ether, + address(this) + ); + + assertEq(perpsMarket.debt(accountId), debt); + assertEq(perpsMarket.totalCollateralValue(accountId), tcvBefore); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + assertTrue( + perpsMarket.hasPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + + assertEq(weth.balanceOf(address(this)), wethBefore); + assertEq(usdc.balanceOf(address(this)), usdcBefore); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testUnwindSmallDebtNoOI_ZapNotPermitted_Fail() + public + selectFork(FORK["0p01_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address accountOwner = smallAccountNoOiOwner; + + uint256 tcvBefore = perpsMarket.totalCollateralValue(accountId); + uint256 debt = perpsMarket.debt(accountId); + uint256 collateralAmount = perpsMarket.getCollateralAmount(accountId, 4); + uint256 wethBefore = weth.balanceOf(address(this)); + uint256 usdcBefore = usdc.balanceOf(address(this)); + + assertGt(tcvBefore, 0); + assertGt(debt, 0); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + + vm.startPrank(accountOwner); + vm.expectRevert( + abi.encodeWithSelector( + IPerpsMarket.PermissionDenied.selector, + 170_141_183_460_469_231_731_687_303_715_884_106_052, + _PERPS_MODIFY_COLLATERAL_PERMISSION, + 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + ) + ); + zap.unwind( + accountId, + 4, + collateralAmount, + address(weth), + swapData_0p001_zap, + 0, + 0, + 0.001 ether, + address(this) + ); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt); + assertEq(perpsMarket.totalCollateralValue(accountId), tcvBefore); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + assertFalse( + perpsMarket.hasPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + + assertEq(weth.balanceOf(address(this)), wethBefore); + assertEq(usdc.balanceOf(address(this)), usdcBefore); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + error OnlyAave(address); + + function testUnwindFailDirectCallback() + public + selectFork(FORK["0p01_zap"]) + { + vm.expectRevert( + abi.encodeWithSelector(OnlyAave.selector, address(this)) + ); + zap.executeOperation(address(0), 0, 0, address(0), ""); + } + + error ReentrancyDetected(uint8, uint8); + + function testUnwindFailDirectCallbackInvalidOrigin() + public + selectFork(FORK["0p01_zap"]) + { + vm.startPrank(ARBITRUM_AAVE_POOL); + vm.expectRevert( + abi.encodeWithSelector(ReentrancyDetected.selector, 0, 1) + ); + zap.executeOperation(address(0), 0, 0, address(0), ""); + vm.stopPrank(); + } + + function testUnwindLargeDebtNoOI_Overpay_AccountOwner_Success() + public + selectFork(FORK["10_zap"]) + { + uint128 accountId = largeAccountIdNoOi; + address accountOwner = largeAccountNoOiOwner; + + uint256 tcvBefore = perpsMarket.totalCollateralValue(accountId); + uint256 debt = perpsMarket.debt(accountId); + uint256 collateralAmount = perpsMarket.getCollateralAmount(accountId, 4); + uint256 wethBefore = weth.balanceOf(address(this)); + uint256 usdcBefore = usdc.balanceOf(address(this)); + + assertGt(tcvBefore, 0); + assertGt(debt, 0); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + + vm.startPrank(accountOwner); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + + zap.unwind( + accountId, + 4, // sETH collateral ID + collateralAmount, + address(weth), + swapData_10_zap, + 0, + 0, + 10 ether, + address(this) + ); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(perpsMarket.totalCollateralValue(accountId), 0); + assertEq(perpsMarket.totalAccountOpenInterest(accountId), 0); + assertFalse( + perpsMarket.hasPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + + assertEq( + weth.balanceOf(address(this)), + wethBefore + collateralAmount - 10 ether + ); + assertGt( + usdc.balanceOf(address(this)), + usdcBefore + ((1000 - 2) * outAmount_10_zap / 1000) - (debt / 1e12) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertLt(usdx.balanceOf(address(zap)), 1 ether); + } + + function testUnwindLargeDebtWithOI_OverPay_AccountOwner_Success() + public + selectFork(FORK["10_zap"]) + { + uint128 accountId = largeAccountIdWithOi; + address accountOwner = largeAccountWithOiOwner; + + uint256 tcvBefore = perpsMarket.totalCollateralValue(accountId); + uint256 debt = perpsMarket.debt(accountId); + uint256 collateralAmount = perpsMarket.getCollateralAmount(accountId, 4); + uint256 wethBefore = weth.balanceOf(address(this)); + uint256 usdcBefore = usdc.balanceOf(address(this)); + + assertGt(tcvBefore, 0); + assertGt(debt, 0); + assertGt(perpsMarket.totalAccountOpenInterest(accountId), 0); + + vm.startPrank(accountOwner); + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + + zap.unwind( + accountId, + 4, // sETH collateral ID + 10 ether, + address(weth), + swapData_10_zap, + 0, + 0, + 10 ether, + address(this) + ); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq( + perpsMarket.getCollateralAmount(accountId, 4), + collateralAmount - 10 ether + ); + assertGt( + perpsMarket.totalCollateralValue(accountId), + tcvBefore - outValue_10_zap + ); // this check is weak because the oracle price on this block is diff + // from the odos api price + assertGt(perpsMarket.totalAccountOpenInterest(accountId), 0); + assertFalse( + perpsMarket.hasPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + + assertEq(weth.balanceOf(address(this)), wethBefore); + assertGt( + usdc.balanceOf(address(this)), + usdcBefore + ((1000 - 1) * outAmount_10_zap / 1000) - (debt / 1e12) + ); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertLt(usdx.balanceOf(address(zap)), 1 ether); + } + + function testBurnSmallDebtNoOI_Exact_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address accountOwner = smallAccountNoOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(accountOwner), balanceBefore - debt); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtNoOI_Exact_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(user), balanceBefore - debt); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtNoOI_Overpay_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address accountOwner = smallAccountNoOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = 123_456_789 + debt; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(accountOwner), balanceBefore - debt); + assertEq(usdx.balanceOf(accountOwner), 123_456_789); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtNoOI_Overpay_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = 123_456_789 + debt; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(user), balanceBefore - debt); + assertEq(usdx.balanceOf(user), 123_456_789); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtNoOI_Partial_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address accountOwner = smallAccountNoOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt - 1e6; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt - balanceBefore); + assertEq(usdx.balanceOf(accountOwner), 0); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtNoOI_Partial_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdNoOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt - 1e6; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt - balanceBefore); + assertEq(usdx.balanceOf(user), 0); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtWithOI_Exact_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdWithOi; + address accountOwner = smallAccountWithOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(accountOwner), balanceBefore - debt); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtWithOI_Exact_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdWithOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(user), balanceBefore - debt); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtWithOI_Overpay_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdWithOi; + address accountOwner = smallAccountWithOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = 123_456_789 + debt; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(accountOwner), balanceBefore - debt); + assertEq(usdx.balanceOf(accountOwner), 123_456_789); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtWithOI_Overpay_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdWithOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = 123_456_789 + debt; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(user), balanceBefore - debt); + assertEq(usdx.balanceOf(user), 123_456_789); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtWithOI_Partial_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdWithOi; + address accountOwner = smallAccountWithOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt - 1e6; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt - balanceBefore); + assertEq(usdx.balanceOf(accountOwner), 0); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnSmallDebtWithOI_Partial_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = smallAccountIdWithOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt - 1e6; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt - balanceBefore); + assertEq(usdx.balanceOf(user), 0); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtNoOI_Exact_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdNoOi; + address accountOwner = largeAccountNoOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(accountOwner), balanceBefore - debt); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtNoOI_Exact_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdNoOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(user), balanceBefore - debt); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtNoOI_Overpay_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdNoOi; + address accountOwner = largeAccountNoOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = 123_456_789 + debt; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(accountOwner), balanceBefore - debt); + assertEq(usdx.balanceOf(accountOwner), 123_456_789); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtNoOI_Overpay_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdNoOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = 123_456_789 + debt; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(user), balanceBefore - debt); + assertEq(usdx.balanceOf(user), 123_456_789); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtNoOI_Partial_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdNoOi; + address accountOwner = largeAccountNoOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt - 1e6; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt - balanceBefore); + assertEq(usdx.balanceOf(accountOwner), 0); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtNoOI_Partial_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdNoOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt - 1e6; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt - balanceBefore); + assertEq(usdx.balanceOf(user), 0); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtWithOI_Exact_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdWithOi; + address accountOwner = largeAccountWithOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(accountOwner), balanceBefore - debt); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtWithOI_Exact_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdWithOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(user), balanceBefore - debt); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtWithOI_Overpay_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdWithOi; + address accountOwner = largeAccountWithOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = 123_456_789 + debt; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(accountOwner), balanceBefore - debt); + assertEq(usdx.balanceOf(accountOwner), 123_456_789); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtWithOI_Overpay_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdWithOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = 123_456_789 + debt; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), 0); + assertEq(usdx.balanceOf(user), balanceBefore - debt); + assertEq(usdx.balanceOf(user), 123_456_789); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtWithOI_Partial_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdWithOi; + address accountOwner = largeAccountWithOiOwner; + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt - 1e6; + + _spin(accountOwner, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(accountOwner); + + vm.startPrank(accountOwner); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt - balanceBefore); + assertEq(usdx.balanceOf(accountOwner), 0); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testBurnLargeDebtWithOI_Partial_NotAccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + uint128 accountId = largeAccountIdWithOi; + address user = vm.addr(1234); + + // check account debt + uint256 debt = perpsMarket.debt(accountId); + + uint256 amount = debt - 1e6; + + _spin(user, usdx, amount); + uint256 balanceBefore = usdx.balanceOf(user); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + zap.burn(amount, accountId); + vm.stopPrank(); + + assertEq(perpsMarket.debt(accountId), debt - balanceBefore); + assertEq(usdx.balanceOf(user), 0); + + assertEq(weth.balanceOf(address(zap)), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + // TODO fix plumber tests + + function testFlush_Plumber_Success() public selectFork(FORK["1_zap"]) { + uint256 amountWeth = 1234 ether; + uint256 amountUsdx = 56_789 ether; + _spin(address(zap), weth, amountWeth); + _spin(address(zap), usdx, amountUsdx); + + uint256 zapWethBefore = weth.balanceOf(address(zap)); + uint256 zapUsdxBefore = usdx.balanceOf(address(zap)); + + uint256 plumberWethBefore = weth.balanceOf(address(this)); + uint256 plumberUsdxBefore = usdx.balanceOf(address(this)); + + // this test contract created the Zap contract and should be the owner + zap.flush(address(usdx)); + + assertEq(usdx.balanceOf(address(zap)), 0); + assertEq(weth.balanceOf(address(zap)), zapWethBefore); + + assertEq( + usdx.balanceOf(address(this)), plumberUsdxBefore + zapUsdxBefore + ); + assertEq(weth.balanceOf(address(this)), plumberWethBefore); + + zap.flush(address(weth)); + + assertEq(usdx.balanceOf(address(zap)), 0); + assertEq(weth.balanceOf(address(zap)), 0); + + assertEq( + usdx.balanceOf(address(this)), plumberUsdxBefore + zapUsdxBefore + ); + assertEq( + weth.balanceOf(address(this)), plumberWethBefore + zapWethBefore + ); + } + + error OnlyPlumber(); + + function testFlush_NotPlumberer_Fail() public selectFork(FORK["1_zap"]) { + uint256 amountWeth = 1234 ether; + uint256 amountUsdx = 56_789 ether; + _spin(address(zap), weth, amountWeth); + _spin(address(zap), usdx, amountUsdx); + + uint256 zapWethBefore = weth.balanceOf(address(zap)); + uint256 zapUsdxBefore = usdx.balanceOf(address(zap)); + + uint256 plumberWethBefore = weth.balanceOf(address(this)); + uint256 plumberUsdxBefore = usdx.balanceOf(address(this)); + + // this test contract created the Zap contract and should be the owner + vm.prank(vm.addr(987_654_321)); + vm.expectRevert(abi.encodeWithSelector(OnlyPlumber.selector)); + zap.flush(address(usdx)); + + assertEq(usdx.balanceOf(address(zap)), zapUsdxBefore); + assertEq(weth.balanceOf(address(zap)), zapWethBefore); + + assertEq(usdx.balanceOf(address(this)), plumberUsdxBefore); + assertEq(weth.balanceOf(address(this)), plumberWethBefore); + } + + function testFlushNominate_Plumber_Success() + public + selectFork(FORK["1_zap"]) + { + address currentPlumber = zap.PLUMBER(); + address nominated = address(0xdeadbeef); + + assertGt(uint160(currentPlumber), 0); + + zap.nominatePlumber(nominated); + + address nominatedPlumber = zap.nominatedPlumber(); + assertEq(nominatedPlumber, nominated); + assertEq(currentPlumber, zap.PLUMBER()); + + vm.prank(nominated); + zap.acceptPlumberNomination(); + address newPlumber = zap.PLUMBER(); + assertEq(nominated, newPlumber); + } + + function testFlushDesignate_NotPlumber_Fail() + public + selectFork(FORK["1_zap"]) + { + address currentPlumber = zap.PLUMBER(); + address nominated = address(0); + + assertGt(uint160(currentPlumber), 0); + + vm.prank(vm.addr(987_654_321)); + vm.expectRevert(abi.encodeWithSelector(OnlyPlumber.selector)); + zap.nominatePlumber(nominated); + + address newPlumber = zap.PLUMBER(); + assertEq(newPlumber, currentPlumber); + assertGt(uint160(newPlumber), 0); + } + + function testWithdrawNoOiNoDebt_AllCollateral_AccountOwner_Success() + public + selectFork(FORK["1_zap"]) + { + address user = vm.addr(5); + uint32 amount = 1_000_000_000; + _spin(user, usdx, amount); + + vm.startPrank(user); + uint128 accountId = perpsMarket.createAccount(); + int128 margin = int128(int32(amount)); + + usdx.approve(address(perpsMarket), amount); + perpsMarket.modifyCollateral(accountId, 0, margin); + + assertEq(usdx.balanceOf(user), 0); + assertEq(perpsMarket.totalCollateralValue(accountId), amount); + + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + zap.withdraw({ + _synthId: 0, + _amount: amount, + _accountId: accountId, + _receiver: user + }); + vm.stopPrank(); + + assertEq(usdx.balanceOf(user), amount); + assertEq(perpsMarket.totalCollateralValue(accountId), 0); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + assertFalse( + perpsMarket.isAuthorized( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + } + + function testWithdrawNoOiNoDebt_AllCollateral_NotAccounttOwner_Fail() + public + selectFork(FORK["1_zap"]) + { + address user = vm.addr(5); + uint32 amount = 1_000_000_000; + _spin(user, usdx, amount); + + vm.startPrank(user); + uint128 accountId = perpsMarket.createAccount(); + int128 margin = int128(int32(amount)); + + usdx.approve(address(perpsMarket), amount); + perpsMarket.modifyCollateral(accountId, 0, margin); + + assertEq(usdx.balanceOf(user), 0); + + perpsMarket.grantPermission( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ); + vm.stopPrank(); + + vm.expectRevert(abi.encodeWithSelector(NotPermitted.selector)); + zap.withdraw({ + _synthId: 0, + _amount: amount, + _accountId: accountId, + _receiver: user + }); + + assertEq(usdx.balanceOf(user), 0); + assertEq(perpsMarket.totalCollateralValue(accountId), amount); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + assertTrue( + perpsMarket.isAuthorized( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + } + + function testWithdrawNoOiNoDebt_AccountOwner_ZapNotPermitted_Fail() + public + selectFork(FORK["1_zap"]) + { + address user = vm.addr(5); + uint32 amount = 1_000_000_000; + _spin(user, usdx, amount); + + vm.startPrank(user); + uint128 accountId = perpsMarket.createAccount(); + int128 margin = int128(int32(amount)); + + usdx.approve(address(perpsMarket), amount); + perpsMarket.modifyCollateral(accountId, 0, margin); + + assertEq(usdx.balanceOf(user), 0); + assertEq(perpsMarket.totalCollateralValue(accountId), amount); + + vm.expectRevert( + abi.encodeWithSelector( + IPerpsMarket.PermissionDenied.selector, + accountId, + _PERPS_MODIFY_COLLATERAL_PERMISSION, + address(zap) + ) + ); + zap.withdraw({ + _synthId: 0, + _amount: amount, + _accountId: accountId, + _receiver: user + }); + + vm.stopPrank(); + + assertEq(usdx.balanceOf(user), 0); + assertEq(perpsMarket.totalCollateralValue(accountId), amount); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + assertFalse( + perpsMarket.isAuthorized( + accountId, _PERPS_MODIFY_COLLATERAL_PERMISSION, address(zap) + ) + ); + } + + // Basic functional tests, these are similar to existing tests and were used + // to ensure this harness worked correctly + function testBuySuccess(uint128 amount) public selectFork(FORK["1_zap"]) { + // D18, fuzzing up to 3e38 + vm.assume(amount < uint128(type(int128).max / 9)); + vm.assume(amount > 10); + + address user = vm.addr(1); + // uint256 amount = 1000e6; + + _spin(user, usdx, amount); + + assertEq(usdx.balanceOf(user), amount); + assertEq(susdc.balanceOf(user), 0); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + + (uint256 received, address synth) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _minAmountOut: 0, + _receiver: user + }); + vm.stopPrank(); + + assertEq(synth, address(susdc)); + assertGe(received, amount * 9 / 10); + assertEq(usdx.balanceOf(user), 0); + assertGe(susdc.balanceOf(user), amount * 9 / 10); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testSellSuccess( /*uint128 amount*/ ) + public + selectFork(FORK["1_zap"]) + { + address user = vm.addr(2); + // _maxCoreCapacity(); + // vm.assume(amount < 1e20); + uint256 amount = 1000e18; + uint256 minAmountOut = amount * 99 / 100; + _spin(user, usdx, amount); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + + (uint256 received,) = zap.buy({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _minAmountOut: minAmountOut, + _receiver: user + }); + + assertEq(usdx.balanceOf(user), 0); + assertEq(usdc.balanceOf(user), 0); + assertEq(susdc.balanceOf(user), received); + assertGe(received, minAmountOut); + assertGe(susdc.balanceOf(user), minAmountOut); + + susdc.approve(address(zap), received); + minAmountOut = received * 99 / 100; + + received = zap.sell({ + _synthId: zap.SUSDC_SPOT_ID(), + _amount: received, + _minAmountOut: minAmountOut, + _receiver: user + }); + vm.stopPrank(); + + assertGe(usdx.balanceOf(user), minAmountOut); + assertGe(received, minAmountOut); + assertEq(usdx.balanceOf(user), received); + assertEq(susdc.balanceOf(user), 0); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testUnwrap( /* uint32 amount */ ) + public + selectFork(FORK["1_zap"]) + { + address user = vm.addr(4); + uint256 amount = 1000e6; + _spin(user, usdc, amount); + + vm.startPrank(user); + usdc.approve(address(zap), amount); + + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _minAmountOut: amount, + _receiver: user + }); + + assertEq(usdc.balanceOf(user), 0); + assertEq(wrapped, amount * 1e12); + assertEq(susdc.balanceOf(user), amount * 1e12); + + susdc.approve(address(zap), type(uint256).max); + + uint256 unwrapped = zap.unwrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: wrapped, + _minAmountOut: amount, + _receiver: user + }); + vm.stopPrank(); + + assertEq(unwrapped, amount); + assertEq(usdc.balanceOf(user), amount); + assertEq(susdc.balanceOf(user), 0); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testWrap( /*uint64 amount*/ ) public selectFork(FORK["1_zap"]) { + address user = vm.addr(6); + uint64 amount = 1000e6; + _spin(user, usdc, amount); + + assertEq(usdc.balanceOf(user), amount); + assertEq(susdc.balanceOf(user), 0); + + vm.startPrank(user); + usdc.approve(address(zap), amount); + + uint256 wrapped = zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount, + _minAmountOut: amount, + _receiver: user + }); + vm.stopPrank(); + + assertEq(wrapped, uint256(amount) * 1e12); + assertEq(usdc.balanceOf(user), 0); + assertEq(susdc.balanceOf(user), wrapped); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testZapInSuccess( /*uint64 amount*/ ) + public + selectFork(FORK["1_zap"]) + { + address user = vm.addr(7); + uint64 amount = 1000e6; + _spin(user, usdc, amount); + + assertEq(usdc.balanceOf(user), amount); + assertEq(usdx.balanceOf(user), 0); + + vm.startPrank(user); + usdc.approve(address(zap), amount); + + uint256 zapped = + zap.zapIn({_amount: amount, _minAmountOut: amount, _receiver: user}); + vm.stopPrank(); + + assertGe(zapped, uint256(amount) * 99e10); // amount * 1e12 * 0.99 + assertEq(usdc.balanceOf(user), 0); + assertEq(usdx.balanceOf(user), zapped); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + + function testZapOutSuccess( /*uint64 amount*/ ) + public + selectFork(FORK["1_zap"]) + { + address user = vm.addr(8); + // vm.assume(amount > 1e12); + uint256 amount = 250e18; + _spin(address(this), usdc, amount / 1e12); + usdc.approve(address(zap), amount / 1e12); + zap.wrap({ + _token: address(usdc), + _synthId: zap.SUSDC_SPOT_ID(), + _amount: amount / 1e12, + _minAmountOut: amount / 1e12, + _receiver: address(this) + }); + assertEq(usdc.balanceOf(address(this)), 0); + assertEq(susdc.balanceOf(address(this)), amount); + + _spin(user, usdx, amount); + + assertEq(usdc.balanceOf(user), 0); + assertEq(usdx.balanceOf(user), amount); + + vm.startPrank(user); + usdx.approve(address(zap), amount); + + uint256 zapped = zap.zapOut({ + _amount: amount, + _minAmountOut: amount * 9 / 1e13, + _receiver: user + }); + vm.stopPrank(); + + assertGe(zapped * 1e12, amount * 9 / 10); + assertEq(usdc.balanceOf(user), zapped); + assertEq(usdx.balanceOf(user), 0); + + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(susdc.balanceOf(address(zap)), 0); + assertEq(usdx.balanceOf(address(zap)), 0); + } + +} diff --git a/test/OdosAPITest.t.sol b/test/OdosAPITest.t.sol new file mode 100644 index 0000000..6563a23 --- /dev/null +++ b/test/OdosAPITest.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Bootstrap, + Constants, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; + +contract OdosAPITest is Bootstrap { + + function test_odos_api_base() public base { + (uint256 quoteStatus, bytes memory quoteData) = getOdosQuote( + BASE_CHAIN_ID, + BASE_WETH, + 1 ether, + BASE_USDC, + DEFAULT_PROPORTION, + DEFAULT_SLIPPAGE, + address(zap) + ); + + assertEq(quoteStatus, 200); + + string memory pathId = + abi.decode(vm.parseJson(string(quoteData), ".pathId"), (string)); + + (uint256 assembleStatus,) = odosAssemble(pathId); + + assertEq(assembleStatus, 200); + } + + function test_odos_api_arbitrum() public arbitrum { + (uint256 quoteStatus, bytes memory quoteData) = getOdosQuote( + ARBITRUM_CHAIN_ID, + ARBITRUM_WETH, + 1 ether, + ARBITRUM_USDC, + DEFAULT_PROPORTION, + DEFAULT_SLIPPAGE, + address(zap) + ); + + assertEq(quoteStatus, 200); + + string memory pathId = + abi.decode(vm.parseJson(string(quoteData), ".pathId"), (string)); + + (uint256 assembleStatus,) = odosAssemble(pathId); + + assertEq(assembleStatus, 200); + } + +} diff --git a/test/Swap.for.t.sol b/test/Swap.for.t.sol deleted file mode 100644 index 8892a86..0000000 --- a/test/Swap.for.t.sol +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import { - Bootstrap, - Constants, - IERC20, - IFactory, - IFactory, - IPerpsMarket, - IPool, - IRouter, - ISpotMarket, - Test, - Zap -} from "./utils/Bootstrap.sol"; - -contract SwapForTest is Bootstrap { - - function test_swap_for_single_base() public base { - uint256 amount = 100e6; - uint256 _maxAmountIn = type(uint256).max / 4; - _spin(ACTOR, weth, _maxAmountIn, address(zap)); - assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), _maxAmountIn); - vm.startPrank(ACTOR); - zap.swapFor({ - _from: address(weth), - _path: abi.encodePacked(address(usdc), FEE_30, address(weth)), - _amount: amount, - _maxAmountIn: _maxAmountIn, - _receiver: ACTOR - }); - assertGt(usdc.balanceOf(ACTOR), 0); - assertLt(weth.balanceOf(ACTOR), _maxAmountIn); - assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); - vm.stopPrank(); - } - - function test_swap_for_single_arbitrum(uint8 percentage) public arbitrum { - vm.assume(percentage < 95 && percentage > 0); - - uint256 _maxAmountIn = type(uint256).max; - _spin(ACTOR, weth, _maxAmountIn, address(zap)); - - address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( - address(weth), address(usdc), zap.FEE_TIER() - ); - uint256 depth = usdc.balanceOf(pool); - uint256 amount = depth * (percentage / 100); - - assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), _maxAmountIn); - - vm.startPrank(ACTOR); - - if (amount == 0) { - vm.expectRevert(); - } - - zap.swapFor({ - _from: address(weth), - _path: abi.encodePacked(address(usdc), FEE_5, address(weth)), - _amount: amount, - _maxAmountIn: _maxAmountIn, - _receiver: ACTOR - }); - - assertTrue( - amount > 1e6 - ? usdc.balanceOf(ACTOR) < 0 - : usdc.balanceOf(ACTOR) == 0 - ); - assertLe(weth.balanceOf(ACTOR), _maxAmountIn); - assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); - - vm.stopPrank(); - } - - /// @custom:todo - function test_swap_for_single_arbitrum_sepolia() public arbitrum_sepolia {} - - function test_swap_for_multihop_base() public base { - uint256 amount = 100e6; - uint256 _maxAmountIn = type(uint256).max / 4; - _spin(ACTOR, tbtc, _maxAmountIn, address(zap)); - assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(tbtc.balanceOf(ACTOR), _maxAmountIn); - vm.startPrank(ACTOR); - zap.swapFor({ - _from: address(tbtc), - _path: abi.encodePacked( - address(usdc), FEE_30, address(weth), FEE_30, address(tbtc) - ), - _amount: amount, - _maxAmountIn: _maxAmountIn, - _receiver: ACTOR - }); - assertGt(usdc.balanceOf(ACTOR), 0); - assertLt(tbtc.balanceOf(ACTOR), _maxAmountIn); - assertEq(tbtc.allowance(address(zap), zap.ROUTER()), 0); - vm.stopPrank(); - } - - function test_swap_for_multihop_arbitrum() public arbitrum { - uint256 amount = 100e6; - uint256 _maxAmountIn = type(uint256).max / 4; - _spin(ACTOR, tbtc, _maxAmountIn, address(zap)); - assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(tbtc.balanceOf(ACTOR), _maxAmountIn); - - vm.startPrank(ACTOR); - zap.swapFor({ - _from: address(tbtc), - _path: abi.encodePacked( - address(usdc), FEE_5, address(weth), FEE_5, address(tbtc) - ), - _amount: amount, - _maxAmountIn: _maxAmountIn, - _receiver: ACTOR - }); - assertGt(usdc.balanceOf(ACTOR), 0); - assertLt(tbtc.balanceOf(ACTOR), _maxAmountIn); - assertEq(tbtc.allowance(address(zap), zap.ROUTER()), 0); - vm.stopPrank(); - } - - bytes32 internal constant POOL_INIT_CODE_HASH = - 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; - - struct PoolKey { - address token0; - address token1; - uint24 fee; - } - - function getPoolKey( - address tokenA, - address tokenB, - uint24 fee - ) - internal - pure - returns (PoolKey memory) - { - if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); - return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); - } - - function computeAddress( - address factory, - PoolKey memory key - ) - internal - pure - returns (address pool) - { - require(key.token0 < key.token1); - uint256 pool256 = uint256( - keccak256( - abi.encodePacked( - hex"ff", - factory, - keccak256(abi.encode(key.token0, key.token1, key.fee)), - POOL_INIT_CODE_HASH - ) - ) - ); - assembly { - pool := shr(96, pool256) - } - } - -} diff --git a/test/Swap.from.t.sol b/test/Swap.from.t.sol new file mode 100644 index 0000000..de7ff6a --- /dev/null +++ b/test/Swap.from.t.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + Constants, + IERC20, + IPerpsMarket, + IPool, + ISpotMarket, + Test, + Zap +} from "./utils/Bootstrap.sol"; +import {BootstrapWithCurrentBlock} from "./utils/BootstrapWithCurrentBlock.sol"; + +contract SwapFromTest is BootstrapWithCurrentBlock { + + bytes swapPath; + string pathId; + + function test_swap_from_weth_base() public base { + { + _spin(ACTOR, weth, DEFAULT_AMOUNT, address(zap)); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(weth.balanceOf(ACTOR), DEFAULT_AMOUNT); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(weth.balanceOf(address(zap)), 0); + + pathId = getOdosQuotePathId( + BASE_CHAIN_ID, BASE_WETH, DEFAULT_AMOUNT, BASE_USDC + ); + + swapPath = getAssemblePath(pathId); + } + + vm.startPrank(ACTOR); + uint256 amountOut = zap.swapFrom({ + _from: BASE_WETH, + _path: swapPath, + _amountIn: DEFAULT_AMOUNT, + _receiver: ACTOR + }); + + assertEq(usdc.balanceOf(ACTOR), amountOut); + assertEq(weth.balanceOf(ACTOR), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(weth.balanceOf(address(zap)), 0); + } + + function test_swap_from_weth_arbitrum() public arbitrum { + { + _spin(ACTOR, weth, DEFAULT_AMOUNT, address(zap)); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(weth.balanceOf(ACTOR), DEFAULT_AMOUNT); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(weth.balanceOf(address(zap)), 0); + + pathId = getOdosQuotePathId( + ARBITRUM_CHAIN_ID, ARBITRUM_WETH, DEFAULT_AMOUNT, ARBITRUM_USDC + ); + + swapPath = getAssemblePath(pathId); + } + + vm.startPrank(ACTOR); + uint256 amountOut = zap.swapFrom({ + _from: ARBITRUM_WETH, + _path: swapPath, + _amountIn: DEFAULT_AMOUNT, + _receiver: ACTOR + }); + + assertEq(usdc.balanceOf(ACTOR), amountOut); + assertEq(weth.balanceOf(ACTOR), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(weth.balanceOf(address(zap)), 0); + } + + function test_swap_from_tbtc_arbitrum() public arbitrum { + { + _spin(ACTOR, tbtc, DEFAULT_AMOUNT, address(zap)); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(tbtc.balanceOf(ACTOR), DEFAULT_AMOUNT); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(tbtc.balanceOf(address(zap)), 0); + + pathId = getOdosQuotePathId( + ARBITRUM_CHAIN_ID, ARBITRUM_TBTC, DEFAULT_AMOUNT, ARBITRUM_USDC + ); + + swapPath = getAssemblePath(pathId); + } + + vm.startPrank(ACTOR); + uint256 amountOut = zap.swapFrom({ + _from: ARBITRUM_TBTC, + _path: swapPath, + _amountIn: DEFAULT_AMOUNT, + _receiver: ACTOR + }); + + assertEq(usdc.balanceOf(ACTOR), amountOut); + assertEq(tbtc.balanceOf(ACTOR), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(tbtc.balanceOf(address(zap)), 0); + } + + function test_swap_from_tbtc_base() public base { + { + _spin(ACTOR, tbtc, DEFAULT_AMOUNT, address(zap)); + assertEq(usdc.balanceOf(ACTOR), 0); + assertEq(tbtc.balanceOf(ACTOR), DEFAULT_AMOUNT); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(tbtc.balanceOf(address(zap)), 0); + + pathId = getOdosQuotePathId( + BASE_CHAIN_ID, BASE_TBTC, DEFAULT_AMOUNT, BASE_USDC + ); + + swapPath = getAssemblePath(pathId); + } + + vm.startPrank(ACTOR); + uint256 amountOut = zap.swapFrom({ + _from: BASE_TBTC, + _path: swapPath, + _amountIn: DEFAULT_AMOUNT, + _receiver: ACTOR + }); + + assertEq(usdc.balanceOf(ACTOR), amountOut); + assertEq(tbtc.balanceOf(ACTOR), 0); + assertEq(usdc.balanceOf(address(zap)), 0); + assertEq(tbtc.balanceOf(address(zap)), 0); + } + +} diff --git a/test/Swap.with.t.sol b/test/Swap.with.t.sol deleted file mode 100644 index 37fae04..0000000 --- a/test/Swap.with.t.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import { - Bootstrap, - Constants, - IERC20, - IFactory, - IPerpsMarket, - IPool, - IRouter, - ISpotMarket, - Test, - Zap -} from "./utils/Bootstrap.sol"; - -contract SwapWithTest is Bootstrap { - - /// @custom:todo - function test_swap_with_base(uint8 percentage) public base { - vm.assume(percentage < 95 && percentage > 0); - - uint256 amountOutMinimum = type(uint256).max; - _spin(ACTOR, weth, amountOutMinimum, address(zap)); - - address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( - address(weth), address(usdc), zap.FEE_TIER() - ); - uint256 depth = usdc.balanceOf(pool); - uint256 amount = depth * (percentage / 100); - - assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), amountOutMinimum); - - vm.startPrank(ACTOR); - - if (amount == 0) { - vm.expectRevert(); - } - - zap.swapWith({ - _from: address(weth), - _path: abi.encodePacked(address(weth), FEE_30, address(usdx)), - _amount: amount, - _amountOutMinimum: amountOutMinimum, - _receiver: ACTOR - }); - - assertTrue( - amount > 1e6 - ? usdc.balanceOf(ACTOR) < 0 - : usdc.balanceOf(ACTOR) == 0 - ); - assertLe(weth.balanceOf(ACTOR), amountOutMinimum); - assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); - - vm.stopPrank(); - } - - /// @custom:todo - function test_swap_with_arbitrum(uint8 percentage) public arbitrum { - vm.assume(percentage < 95 && percentage > 0); - - uint256 amountOutMinimum = type(uint256).max; - _spin(ACTOR, weth, amountOutMinimum, address(zap)); - - address pool = IFactory(IRouter(zap.ROUTER()).factory()).getPool( - address(weth), address(usdc), zap.FEE_TIER() - ); - uint256 depth = usdc.balanceOf(pool); - uint256 amount = depth * (percentage / 100); - - assertEq(usdc.balanceOf(ACTOR), 0); - assertEq(weth.balanceOf(ACTOR), amountOutMinimum); - - vm.startPrank(ACTOR); - - if (amount == 0) { - vm.expectRevert(); - } - - zap.swapWith({ - _from: address(weth), - _path: abi.encodePacked(address(weth), FEE_30, address(usdc)), - _amount: amount, - _amountOutMinimum: amountOutMinimum, - _receiver: ACTOR - }); - - assertTrue( - amount > 1e6 - ? usdc.balanceOf(ACTOR) < 0 - : usdc.balanceOf(ACTOR) == 0 - ); - assertLe(weth.balanceOf(ACTOR), amountOutMinimum); - assertEq(weth.allowance(address(zap), zap.ROUTER()), 0); - - vm.stopPrank(); - } - - /// @custom:todo - function test_swap_with_arbitrum_sepolia() public arbitrum_sepolia {} - -} diff --git a/test/Unwind.t.sol b/test/Unwind.t.sol index 21f0e03..bc83f33 100644 --- a/test/Unwind.t.sol +++ b/test/Unwind.t.sol @@ -13,8 +13,28 @@ import { Zap } from "./utils/Bootstrap.sol"; +import {MathLib} from "./utils/MathLib.sol"; + contract UnwindTest is Bootstrap, Errors { + using MathLib for int128; + using MathLib for int256; + using MathLib for uint256; + + address public constant DEBT_ACTOR = + address(0x72A8EA777f5Aa58a1E5a405931e2ccb455B60088); + uint128 public constant ACCOUNT_ID = + 170_141_183_460_469_231_731_687_303_715_884_105_766; + uint256 public constant INITIAL_DEBT = 2_244_714_058_540_271_627; + + address constant USDC_ADDR = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; + address constant WETH_ADDR = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + + uint256 SWAP_AMOUNT = 1_352_346_556_314_334; + + bytes swapPath; + string pathId; + function test_unwind_is_authorized() public arbitrum { vm.prank(ACTOR); vm.expectRevert(NotPermitted.selector); @@ -22,6 +42,47 @@ contract UnwindTest is Bootstrap, Errors { } /// @custom:todo - function test_unwind_arbitrum_sepolia() public arbitrum_sepolia {} + function test_unwind_arbitrum() public arbitrum { + IPerpsMarket perpsMarketProxy = IPerpsMarket(zap.PERPS_MARKET()); + uint256 initialAccountDebt = perpsMarketProxy.debt(ACCOUNT_ID); + assertEq(initialAccountDebt, INITIAL_DEBT); + + int256 withdrawableMargin = + perpsMarketProxy.getWithdrawableMargin(ACCOUNT_ID); + + /// While there is debt, withdrawable margin should be 0 + assertEq(withdrawableMargin, 0); + + int256 availableMargin = perpsMarketProxy.getAvailableMargin(ACCOUNT_ID); + assertGt(availableMargin, 0); + + uint256 balanceBefore = IERC20(ARBITRUM_WETH).balanceOf(DEBT_ACTOR); + + vm.startPrank(DEBT_ACTOR); + + pathId = getOdosQuotePathId( + ARBITRUM_CHAIN_ID, ARBITRUM_WETH, SWAP_AMOUNT, ARBITRUM_USDC + ); + + swapPath = getAssemblePath(pathId); + + zap.unwind({ + _accountId: ACCOUNT_ID, + _collateralId: 4, + _collateralAmount: 36_000_000_000_000_000, + _collateral: WETH_ADDR, + _path: swapPath, + _zapMinAmountOut: 2_222_267_000_000_000_000, + _unwrapMinAmountOut: 35_964_000_000_000_000, + _swapAmountIn: SWAP_AMOUNT, + _receiver: DEBT_ACTOR + }); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(ARBITRUM_WETH).balanceOf(DEBT_ACTOR); + + assertGt(balanceAfter, balanceBefore); + } } diff --git a/test/interfaces/ISynthetix.sol b/test/interfaces/ISynthetix.sol index 87aaf3c..e91166e 100644 --- a/test/interfaces/ISynthetix.sol +++ b/test/interfaces/ISynthetix.sol @@ -53,6 +53,13 @@ interface ISpotMarket { interface IPerpsMarket { + error InsufficientCollateralAvailableForWithdraw( + int256 withdrawableMarginUsd, uint256 requestedMarginUsd + ); + error PermissionDenied( + uint128 accountId, bytes32 permission, address target + ); + function modifyCollateral( uint128 accountId, uint128 synthMarketId, @@ -100,4 +107,30 @@ interface IPerpsMarket { ) external; + function getAvailableMargin(uint128 accountId) + external + returns (int256 availableMargin); + + function getWithdrawableMargin(uint128 accountId) + external + returns (int256 withdrawableMargin); + + function totalCollateralValue(uint128 accountId) + external + view + returns (uint256); + + function totalAccountOpenInterest(uint128 accountId) + external + view + returns (uint256); + + function getCollateralAmount( + uint128 accountId, + uint128 collateralId + ) + external + view + returns (uint256); + } diff --git a/test/interfaces/IUniswap.sol b/test/interfaces/IUniswap.sol deleted file mode 100644 index 150cf9f..0000000 --- a/test/interfaces/IUniswap.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -interface IRouter { - - function factory() external view returns (address); - -} - -interface IFactory { - - function getPool( - address tokenA, - address tokenB, - uint24 fee - ) - external - view - returns (address pool); - -} diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol index d71b65d..1c2e435 100644 --- a/test/utils/Bootstrap.sol +++ b/test/utils/Bootstrap.sol @@ -8,9 +8,11 @@ import { import {Errors, IERC20, IPool, Reentrancy, Zap} from "../../src/Zap.sol"; import {IPerpsMarket, ISpotMarket} from "../interfaces/ISynthetix.sol"; -import {IFactory, IRouter} from "../interfaces/IUniswap.sol"; import {Constants} from "../utils/Constants.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; import {Test} from "forge-std/Test.sol"; +import {Surl} from "surl/src/Surl.sol"; contract Bootstrap is Test, @@ -21,6 +23,9 @@ contract Bootstrap is Constants { + using Surl for *; + using stdJson for string; + /// @custom:forks uint256 BASE; uint256 ARBITRUM; @@ -38,7 +43,9 @@ contract Bootstrap is IERC20 weth; IERC20 tbtc; - function setUp() public { + string[] headers; + + function setUp() public virtual { string memory BASE_RPC = vm.envString(BASE_RPC_REF); string memory ARBITRUM_RPC = vm.envString(ARBITRUM_RPC_REF); string memory ARBITRUM_SEPOLIA_RPC = @@ -48,6 +55,8 @@ contract Bootstrap is ARBITRUM = vm.createFork(ARBITRUM_RPC, ARBITRUM_FORK_BLOCK); ARBITRUM_SEPOLIA = vm.createFork(ARBITRUM_SEPOLIA_RPC, ARBITRUM_SEPOLIA_FORK_BLOCK); + + headers.push("Content-Type: application/json"); } modifier base() { @@ -63,8 +72,7 @@ contract Bootstrap is referrer: BASE_REFERRER, susdcSpotId: BASE_SUSDC_SPOT_MARKET_ID, aave: BASE_AAVE_POOL, - router: BASE_ROUTER, - quoter: BASE_QUOTER + router: BASE_ROUTER }); /// @custom:auxiliary @@ -92,8 +100,7 @@ contract Bootstrap is referrer: ARBITRUM_REFERRER, susdcSpotId: ARBITRUM_SUSDC_SPOT_MARKET_ID, aave: ARBITRUM_AAVE_POOL, - router: ARBITRUM_ROUTER, - quoter: ARBITRUM_QUOTER + router: ARBITRUM_ROUTER }); /// @custom:auxiliary @@ -120,8 +127,7 @@ contract Bootstrap is referrer: ARBITRUM_SEPOLIA_REFERRER, susdcSpotId: ARBITRUM_SEPOLIA_SUSDC_SPOT_MARKET_ID, aave: ARBITRUM_SEPOLIA_AAVE_POOL, - router: ARBITRUM_SEPOLIA_ROUTER, - quoter: ARBITRUM_SEPOLIA_QUOTER + router: ARBITRUM_SEPOLIA_ROUTER }); /// @custom:auxiliary @@ -155,4 +161,116 @@ contract Bootstrap is IERC20(token).approve(approved, type(uint256).max); } + function getOdosQuoteParams( + uint256 chainId, + address tokenIn, + uint256 amountIn, + address tokenOut, + uint256 proportionOut, + uint256 slippageLimitPct, + address userAddress + ) + internal + returns (string memory) + { + return string.concat( + '{"chainId": ', + vm.toString(chainId), + ', "inputTokens": [{"tokenAddress": "', + vm.toString(tokenIn), + '", "amount": "', + vm.toString(amountIn), + '"}],"outputTokens": [{"tokenAddress": "', + vm.toString(tokenOut), + '", "proportion": ', + vm.toString(proportionOut), + '}], "slippageLimitPercent": ', + vm.toString(slippageLimitPct), + ', "userAddr": "', + vm.toString(userAddress), + '"}' + ); + } + + function getOdosQuote( + uint256 chainId, + address tokenIn, + uint256 amountIn, + address tokenOut, + uint256 proportionOut, + uint256 slippageLimitPct, + address userAddress + ) + internal + returns (uint256 status, bytes memory data) + { + string memory params = getOdosQuoteParams( + chainId, + tokenIn, + amountIn, + tokenOut, + proportionOut, + slippageLimitPct, + userAddress + ); + + (status, data) = ODOS_QUOTE_URL.post(headers, params); + } + + function getOdosQuotePathId( + uint256 chainId, + address tokenIn, + uint256 amountIn, + address tokenOut + ) + internal + returns (string memory pathId) + { + (, bytes memory data) = getOdosQuote( + chainId, + tokenIn, + amountIn, + tokenOut, + DEFAULT_PROPORTION, + DEFAULT_SLIPPAGE, + address(zap) + ); + pathId = abi.decode(vm.parseJson(string(data), ".pathId"), (string)); + } + + function getOdosAssembleParams(string memory pathId) + internal + returns (string memory) + { + return string.concat( + '{"userAddr": "', + vm.toString(address(zap)), + '", "pathId": "', + pathId, + '"}' + ); + } + + function odosAssemble(string memory pathId) + internal + returns (uint256 status, bytes memory data) + { + string memory params = getOdosAssembleParams(pathId); + + (status, data) = ODOS_ASSEMBLE_URL.post(headers, params); + } + + function getAssemblePath(string memory pathId) + internal + returns (bytes memory swapPath) + { + bytes memory assembleData; + { + (, assembleData) = odosAssemble(pathId); + } + bytes memory transaction = string(assembleData).parseRaw(".transaction"); + Transaction memory rawTxDetail = abi.decode(transaction, (Transaction)); + return rawTxDetail.data; + } + } diff --git a/test/utils/BootstrapWithCurrentBlock.sol b/test/utils/BootstrapWithCurrentBlock.sol new file mode 100644 index 0000000..918e1cb --- /dev/null +++ b/test/utils/BootstrapWithCurrentBlock.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {Bootstrap} from "./Bootstrap.sol"; +import "forge-std/console2.sol"; + +contract BootstrapWithCurrentBlock is Bootstrap { + + function setUp() public override { + string memory BASE_RPC = vm.envString(BASE_RPC_REF); + string memory ARBITRUM_RPC = vm.envString(ARBITRUM_RPC_REF); + string memory ARBITRUM_SEPOLIA_RPC = + vm.envString(ARBITRUM_SEPOLIA_RPC_REF); + + BASE = vm.createFork(BASE_RPC); + ARBITRUM = vm.createFork(ARBITRUM_RPC); + ARBITRUM_SEPOLIA = vm.createFork(ARBITRUM_SEPOLIA_RPC); + + headers.push("Content-Type: application/json"); + } + +} diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index be5c6fa..92ae830 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -3,14 +3,29 @@ pragma solidity 0.8.27; contract Constants { + struct Transaction { + uint256 chainId; + bytes data; + address from; + uint256 gas; + uint256 gasPrice; + uint256 nonce; + address to; + uint256 value; + } + /// @custom:forks string constant BASE_RPC_REF = "BASE_RPC"; string constant ARBITRUM_RPC_REF = "ARBITRUM_RPC"; string constant ARBITRUM_SEPOLIA_RPC_REF = "ARBITRUM_SEPOLIA_RPC"; + uint256 constant BASE_FORK_BLOCK = 20_165_000; uint256 constant ARBITRUM_FORK_BLOCK = 256_615_000; uint256 constant ARBITRUM_SEPOLIA_FORK_BLOCK = 85_443_000; + uint256 constant BASE_CHAIN_ID = 8453; + uint256 constant ARBITRUM_CHAIN_ID = 42_161; + /// @custom:values address constant ACTOR = 0x7777777777777777777777777777777777777777; uint256 constant DEFAULT_MIN_AMOUNT_OUT = 0; @@ -27,6 +42,10 @@ contract Constants { uint24 constant FEE_30 = 3000; uint24 constant FEE_100 = 10_000; + uint256 DEFAULT_AMOUNT = 1e18; + uint256 DEFAULT_PROPORTION = 1; + uint256 DEFAULT_SLIPPAGE = 1; + /// @custom:synthetix bytes32 constant _ADMIN_PERMISSION = "ADMIN"; bytes32 constant _WITHDRAW_PERMISSION = "WITHDRAW"; @@ -42,4 +61,7 @@ contract Constants { bytes32 constant _BFP_PERPS_SPLIT_ACCOUNT_PERMISSION = "BFP_PERPS_SPLIT_ACCOUNT"; + string constant ODOS_ASSEMBLE_URL = "https://api.odos.xyz/sor/assemble"; + string constant ODOS_QUOTE_URL = "https://api.odos.xyz/sor/quote/v2"; + } diff --git a/test/utils/MathLib.sol b/test/utils/MathLib.sol new file mode 100644 index 0000000..42b7b21 --- /dev/null +++ b/test/utils/MathLib.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.27; + +/// @title Kwenta Smart Margin v3: Math Library for int128 and int256 +/// @author JaredBorders (jaredborders@pm.me) +library MathLib { + + error Overflow(); + + /// @notice get absolute value of the input, returned as an unsigned number. + /// @param x signed 128-bit number + /// @return z unsigned 128-bit absolute value of x + function abs128(int128 x) internal pure returns (uint128 z) { + assembly { + /// shr(127, x): + /// shifts the number x to the right by 127 bits: + /// IF the number is negative, the leftmost bit (bit 127) will be 1 + /// IF the number is positive, the leftmost bit (bit 127) will be 0 + let y := shr(127, x) + + /// sub(xor(x, y), y): + /// IF x is negative, this effectively negates the number + /// IF x is positive, it leaves the number unchanged, thereby + /// computing the absolute value + z := sub(xor(x, y), y) + } + } + + /// @notice get absolute value of the input, returned as an unsigned number. + /// @param x signed 256-bit number + /// @return z unsigned 256-bit absolute value of x + function abs256(int256 x) internal pure returns (uint256 z) { + assembly { + /// shr(255, x): + /// shifts the number x to the right by 255 bits: + /// IF the number is negative, the leftmost bit (bit 255) will be 1 + /// IF the number is positive,the leftmost bit (bit 255) will be 0 + + /// sub(0, shr(255, x)): + /// creates a mask of all 1s if x is negative + /// creates a mask of all 0s if x is positive + let mask := sub(0, shr(255, x)) + + /// If x is negative, this effectively negates the number + /// if x is positive, it leaves the number unchanged, thereby + /// computing the absolute value + z := xor(mask, add(mask, x)) + } + } + + /// @notice determines if input numbers have the same sign + /// @dev asserts that both numbers are not zero + /// @param x signed 128-bit number + /// @param y signed 128-bit number + /// @return true if same sign, false otherwise + function isSameSign(int128 x, int128 y) internal pure returns (bool) { + assert(x != 0 && y != 0); + return (x ^ y) >= 0; + } + + /// @notice safely cast uint256 to int256 + /// @dev reverts if the input is greater than or equal to 2^255 + /// @param x unsigned 256-bit number + /// @return z signed 256-bit number + function toInt256(uint256 x) internal pure returns (int256) { + if (x >= 1 << 255) { + /// @solidity memory-safe-assembly + assembly { + // Store the function selector of `Overflow()`. + mstore(0x00, 0x35278d12) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + } + return int256(x); + } + +} diff --git a/test/utils/OdosSwapData.sol b/test/utils/OdosSwapData.sol new file mode 100644 index 0000000..305e857 --- /dev/null +++ b/test/utils/OdosSwapData.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +contract OdosSwapData { + + // Swap Data for Zap 1 Address + string constant pathId_0p0001_wrong = "47a49af458a7f39dc834967eac3f9d9e"; + uint256 constant blockNumber_0p0001_wrong = 276_777_060; + uint256 constant inAmount_0p0001_wrong = 100_000_000_000_000; + uint256 constant outAmount_0p0001_wrong = 314_813; + uint256 constant inValue_0p0001_wrong = 314_924_000_146_061_120; + uint256 constant outValue_0p0001_wrong = 314_941_831_332_835_264; + int256 constant percentDiff_0p0001_wrong = 5_662_060_295_790_639; + bytes constant swapData_0p0001_wrong = + hex"83bd37f9000b000a065af3107a40000304cdbd00c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000012e234DAe75C793f67A35089C9d99245E1C58470b0000000003010203000b0101010201ff0000000000000000000000000000000000000000009afc3c794d3e5e107e1f85eff2b9779fb556e21982af49447d8a07e3bd95bd0d56f35241523fbab1"; + + string constant pathId_0p001_wrong = "1fff1572b84330ce032fa71a7b8d08c1"; + uint256 constant blockNumber_0p001_wrong = 276_777_060; + uint256 constant inAmount_0p001_wrong = 1_000_000_000_000_000; + uint256 constant outAmount_0p001_wrong = 3_160_741; + uint256 constant inValue_0p001_wrong = 3_149_240_001_460_609_024; + uint256 constant outValue_0p001_wrong = 3_162_035_153_408_974_848; + int256 constant percentDiff_0p001_wrong = 406_293_326_086_043_264; + bytes constant swapData_0p001_wrong = + hex"83bd37f9000b000a07038d7ea4c6800003303aa500c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000012e234DAe75C793f67A35089C9d99245E1C58470b0000000005010207000b010202030102030000000104001e000b0101050601ff0000000000f91107230f06e31ab7213826fd204e0a06c2e0524fc7ac44dae553464d92c049175133239a8705c182af49447d8a07e3bd95bd0d56f35241523fbab1ff970a61a04b1ca14834a43f5de4533ebddb5cc893cce474015007b38da0ecea96671ee4dc3d40ad912ce59144191c1204e64559fe8253a0e49e6548"; + + string constant pathId_0p01_wrong = "744e63f869a7d2e221fa611afa5a7835"; + uint256 constant blockNumber_0p01_wrong = 276_777_100; + uint256 constant inAmount_0p01_wrong = 10_000_000_000_000_000; + uint256 constant outAmount_0p01_wrong = 31_561_056; + uint256 constant inValue_0p01_wrong = 31_493_668_168_667_074_560; + uint256 constant outValue_0p01_wrong = 31_573_960_912_001_150_976; + int256 constant percentDiff_0p01_wrong = 254_948_845_285_539_968; + bytes constant swapData_0p01_wrong = + hex"83bd37f9000b000a072386f26fc100000401e1956000c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000012e234DAe75C793f67A35089C9d99245E1C58470b000000001007040f01209d77d90701000102cc65a812ce382ab909a11e434dbf75b34f1cc59d000200000000000000000001013585c69b0b02000301010194e65ca00b0200040101000b03000501010563f2631907000006070052688295413b32626d226a205b95cdb337de860002000000000000000003d1057e5cb0cf030000010806003d0577f61640030000010906001e040b00000a0600020700000207bcaa6c053cab3dd73a2e898d89a4f84a180ae1ca000100000000000000000458060b00000b0c0001af0689f80b03010d0701000b03010e0701ff000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1040d1edc9569d4bab2d15287dc5a4f10f56a56b840eba28aa6649843214134262c38fb9f400338154fc7ac44dae553464d92c049175133239a8705c1e37304f7489ed253b2a46a1d9dabdca3d311d22eff970a61a04b1ca14834a43f5de4533ebddb5cc8912ce59144191c1204e64559fe8253a0e49e65489cb911cbb270cae0d132689ce11c2c52ab2dedbcf91107230f06e31ab7213826fd204e0a06c2e052e3d2f7c05b818ac79765329c953ef2427714fb5bf92768916015b5ebd9fa54d6ba10da5864e24914e80772eaf6e2e18b651f160bc9158b2a5cafca65b0f6ca40411360c03d41c5ffc5f179b8403cdcf893cce474015007b38da0ecea96671ee4dc3d40ad"; + + string constant pathId_0p1_wrong = "e14c2d1c4bb5cc2724650e035fab3248"; + uint256 constant blockNumber_0p1_wrong = 276_777_100; + uint256 constant inAmount_0p1_wrong = 100_000_000_000_000_000; + uint256 constant outAmount_0p1_wrong = 314_882_743; + uint256 constant inValue_0p1_wrong = 314_936_681_686_670_704_640; + uint256 constant outValue_0p1_wrong = 315_011_485_527_760_371_712; + int256 constant percentDiff_0p1_wrong = 23_752_025_546_571_076; + bytes constant swapData_0p1_wrong = + hex"83bd37f9000b000a08016345785d8a00000412c4bab700c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000012e234DAe75C793f67A35089C9d99245E1C58470b0000000005010207000b0100010201020b0004040500000a01010003060100ff000000000042161084d0672e1d3f26a9b53e653be2084ff19c82af49447d8a07e3bd95bd0d56f35241523fbab11c34204fcfe5314dcf53be2671c02c35db58b4e3fa4971dc5ad81b4fccaffad0a584d13192b7d2bafd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb95d3a1ff2b6bab83b63cd9ad0787074081a52ef34"; + + string constant pathId_1_wrong = "a950e8a07faadf6c994a6fcedce8954f"; + uint256 constant blockNumber_1_wrong = 276_777_120; + uint256 constant inAmount_1_wrong = 1_000_000_000_000_000_000; + uint256 constant outAmount_1_wrong = 3_148_598_125; + uint256 constant inValue_1_wrong = 3_149_459_336_019_107_643_392; + uint256 constant outValue_1_wrong = 3_149_884_285_639_120_125_952; + int256 constant percentDiff_1_wrong = 13_492_780_019_504_380; + bytes constant swapData_1_wrong = + hex"83bd37f9000b000a080de0b6b3a764000004bbabcb6d00c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000012e234DAe75C793f67A35089C9d99245E1C58470b000000001004051401409fa1ee0b0101010201012e86a1df0b01010302010132cd986a0b01010402010140de2d620b020005020101a6648b170b020006020101ea1ff7a90b0200070201000b0300080200060b0000090a0105a3c9ca370b030c0c0d000441040e0d00060a0101000b0f0100080b0101101100000b0101121300ff00000000b1026b8e7276e7ac75410f1fcbbe21796e8f752682af49447d8a07e3bd95bd0d56f35241523fbab16f38e884725a116c9c7fbf208e79fe8828a2595f30afbcf9458c3131a6d051c621e307e6278e41107cccba38e2d959fe135e79aebb57ccb27b12835842161084d0672e1d3f26a9b53e653be2084ff19c389938cf14be379217570d8e4619e51fbdafaa214bfc22a4da7f31f8a912a79a7e44a822398b4390a79fd76ca2b24631ec3151f10c0660a30bc946e72f2a2543b76a4166549f7aab2e75bef0aefc5b0f1c34204fcfe5314dcf53be2671c02c35db58b4e3fa4971dc5ad81b4fccaffad0a584d13192b7d2bafd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9e4b2dfc82977dd2dce7e8d37895a6a8f50cbb4fb5d3a1ff2b6bab83b63cd9ad0787074081a52ef34c86eb7b85807020b4548ee05b54bfc956eebbfcdff970a61a04b1ca14834a43f5de4533ebddb5cc8655c1607f8c2e73d5b4ddabce9ba8792b87592b6f97f4df75117a78c1a5a0dbb814af92458539fb4"; + + string constant pathId_10_wrong = "53e7412067ebe09dea8447d33ae19e3f"; + uint256 constant blockNumber_10_wrong = 276_777_141; + uint256 constant inAmount_10_wrong = 10_000_000_000_000_000_000; + uint256 constant outAmount_10_wrong = 31_489_123_427; + uint256 constant inValue_10_wrong = 31_494_593_360_191_079_579_648; + uint256 constant outValue_10_wrong = 31_501_986_315_803_172_012_032; + int256 constant percentDiff_10_wrong = 23_473_729_371_701_780; + bytes constant swapData_10_wrong = + hex"83bd37f9000b000a088ac7230489e80000050754e5e46300c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000015615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f00000000100504120167b95bfb0b010101020101121232590b0101030201010c5500ad0b010104020101490175bb0b0101050201012726aac24401010106020001249b9ba40b02000702010133c436ae0b0200080201000b020009020105d60ce3904b000a0b0c0100055d86e66141000d0b00040b030f0f0b00060a0101000e100100000b0101110c00ff0000000000000000000000000000000000000000000000000000b1026b8e7276e7ac75410f1fcbbe21796e8f752682af49447d8a07e3bd95bd0d56f35241523fbab16f38e884725a116c9c7fbf208e79fe8828a2595f30afbcf9458c3131a6d051c621e307e6278e41107fcdc35463e3770c2fb992716cd070b63540b9471666ea6e83bcc3473111732ef086ba47e6eddeb77cccba38e2d959fe135e79aebb57ccb27b12835842161084d0672e1d3f26a9b53e653be2084ff19c389938cf14be379217570d8e4619e51fbdafaa217f90122bf0700f9e7e1f688fe926940e8839f353fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9ff970a61a04b1ca14834a43f5de4533ebddb5cc8e4b2dfc82977dd2dce7e8d37895a6a8f50cbb4fb1c34204fcfe5314dcf53be2671c02c35db58b4e3fa4971dc5ad81b4fccaffad0a584d13192b7d2ba5d3a1ff2b6bab83b63cd9ad0787074081a52ef34c86eb7b85807020b4548ee05b54bfc956eebbfcd"; + + string constant pathId_100_wrong = "9ce3d5d49865dd8d766a9b9fab7428cb"; + uint256 constant blockNumber_100_wrong = 276_777_141; + uint256 constant inAmount_100_wrong = 100_000_000_000_000_000_000; + uint256 constant outAmount_100_wrong = 314_703_889_688; + uint256 constant inValue_100_wrong = 314_945_933_601_910_879_682_560; + uint256 constant outValue_100_wrong = 314_832_442_041_314_297_511_936; + int256 constant percentDiff_100_wrong = -36_035_251_923_593_136; + bytes constant swapData_100_wrong = + hex"83bd37f9000b000a09056bc75e2d63100000054945d0451800c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000012e234DAe75C793f67A35089C9d99245E1C58470b00000000280d0a2b01001268600b01000102010105940b830b010003020101109122314801000204011a7c77050b0200050201016be013e20b0200060201010507cf340b020007020101053c52a40b02000802010106f4fe9d0b020009020101004cbabd0b02000a02010114f022e70b02000b0201016d9be18a440200010c0200010b932c5b0b03000d020101a91ef4a40b03000e0201012c5e42380b03000f020101cb4aaeb20b0300100201011a3b2a880b03001102010153bfea020b0400120201010f3050970b05001302010109a5421a0b0500140201010b3418670b05001502010116f8ae700b0600160201010dd8b7db0b0600170201010cb916ba0b0600180201000b0700190200078fbd2eda000007ef2520ad4b081a1b040100075e37b6b141081c1b00060b091e1e1b000200080e00010c0007080708001f04423a1323c871abc9d89eb06855bf5347048fc4a50000000000000000000004960a0b08002021010e170222232400120a0200001d25010011e8b85db00b0200260400100b0200270400004e020001281b007ffffff1020b0200292a01040224ff000000000000521aa84ab3fcc4c05cabac24dc3682339887b12682af49447d8a07e3bd95bd0d56f35241523fbab1c31e54c7a869b9fcbecc14363cf510d1c41fa443ff970a61a04b1ca14834a43f5de4533ebddb5cc8b1026b8e7276e7ac75410f1fcbbe21796e8f7526c6962004f452be9203591991d15f6b388e09e8d06f38e884725a116c9c7fbf208e79fe8828a2595f30afbcf9458c3131a6d051c621e307e6278e4110f3eb87c1f6020982173c908e7eb31aa66c1f0296d9e2a1a61b6e61b275cec326465d417e52c1b95c7fcdc35463e3770c2fb992716cd070b63540b9471666ea6e83bcc3473111732ef086ba47e6eddeb77cccba38e2d959fe135e79aebb57ccb27b128358641c00a822e8b671738d32a431a4fb6074e5c79d42161084d0672e1d3f26a9b53e653be2084ff19c389938cf14be379217570d8e4619e51fbdafaa210bacc7a9717e70ea0da5ac075889bd87d4c8119731fa55e03bad93c7f8affdd2ec616ebfde24600192c63d0e701caae670c9415d91c474f686298f0099543bf98ca1830aa20d3eb12c1b9962f8eadc1111d53ec50bc8f54b9357fbfe2a7de034fc00f8b33dd2fdba71282083d440687cce9e4231aaac534e421803da50d3932caa36bd1731d36a0e2af93542e37304f7489ed253b2a46a1d9dabdca3d311d22e4bfc22a4da7f31f8a912a79a7e44a822398b43907f90122bf0700f9e7e1f688fe926940e8839f353fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9e4b2dfc82977dd2dce7e8d37895a6a8f50cbb4fb1c34204fcfe5314dcf53be2671c02c35db58b4e3fa4971dc5ad81b4fccaffad0a584d13192b7d2bada10009cbd5d07dd0cecc66161fc93d7c9000da1cda53b1f66614552f834ceef361a8d12a0b8dad8912ce59144191c1204e64559fe8253a0e49e654873cb180bf0521828d8849bc8cf2b920918e23032e80772eaf6e2e18b651f160bc9158b2a5cafca65af88d065e77c8cc2239327c5edb3a432268e58315d3a1ff2b6bab83b63cd9ad0787074081a52ef34c86eb7b85807020b4548ee05b54bfc956eebbfcd562d29b54d2c57f8620c920415c4dceadd6de2d2713e1346d585a1dccadc093210e33cd6bc8cf3d10e4831319a50228b9e450861297ab92dee15b44f2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"; + + // Swap data for Zap 2 address + string constant pathId_0p0001_zap = "a4122f9af71c05dfc490281deb5dd4d9"; + uint256 constant blockNumber_0p0001_zap = 276_777_161; + uint256 constant inAmount_0p0001_zap = 100_000_000_000_000; + uint256 constant outAmount_0p0001_zap = 314_994; + uint256 constant inValue_0p0001_zap = 314_945_933_601_911_040; + uint256 constant outValue_0p0001_zap = 315_122_693_741_026_624; + int256 constant percentDiff_0p0001_zap = 56_123_962_958_992_024; + bytes constant swapData_0p0001_zap = + hex"83bd37f9000b000a065af3107a40000304ce7200c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000015615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f0000000003010203000b0101010201ff0000000000000000000000000000000000000000006f38e884725a116c9c7fbf208e79fe8828a2595f82af49447d8a07e3bd95bd0d56f35241523fbab1"; + + string constant pathId_0p001_zap = "75d04c89013e2a1e700f8299a8cc5084"; + uint256 constant blockNumber_0p001_zap = 276_777_161; + uint256 constant inAmount_0p001_zap = 1_000_000_000_000_000; + uint256 constant outAmount_0p001_zap = 3_149_891; + uint256 constant inValue_0p001_zap = 3_149_806_404_622_892_544; + uint256 constant outValue_0p001_zap = 3_151_158_316_506_409_984; + int256 constant percentDiff_0p001_zap = 42_920_475_415_058_944; + bytes constant swapData_0p001_zap = + hex"83bd37f9000b000a07038d7ea4c680000330104300c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000015615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f0000000003010203000b0101010201ff0000000000000000000000000000000000000000006f38e884725a116c9c7fbf208e79fe8828a2595f82af49447d8a07e3bd95bd0d56f35241523fbab1"; + + string constant pathId_0p01_zap = "0fcde9ab590d1edfd1b56d06c0033ad6"; + uint256 constant blockNumber_0p01_zap = 276_777_182; + uint256 constant inAmount_0p01_zap = 10_000_000_000_000_000; + uint256 constant outAmount_0p01_zap = 31_585_692; + uint256 constant inValue_0p01_zap = 31_498_064_046_228_926_464; + uint256 constant outValue_0p01_zap = 31_598_398_956_482_789_376; + int256 constant percentDiff_0p01_zap = 318_543_101_908_105_088; + bytes constant swapData_0p01_zap = + hex"83bd37f9000b000a072386f26fc100000401e1f59c00c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000015615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f000000001007040e01208aae820701000102cc65a812ce382ab909a11e434dbf75b34f1cc59d00020000000000000000000101561e6acb0b0200030101015087af730b0300040101000b0300050101076457c21607000707080052688295413b32626d226a205b95cdb337de860002000000000000000003d1077e20d2f8030007010907003d0777cc7e67030007010a07001e060b00070b0700020700070208bcaa6c053cab3dd73a2e898d89a4f84a180ae1ca000100000000000000000458040b00070c0d000044020100060800ff00000000000000000000000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1040d1edc9569d4bab2d15287dc5a4f10f56a56b83dd2fdba71282083d440687cce9e4231aaac534e40eba28aa6649843214134262c38fb9f400338154fc7ac44dae553464d92c049175133239a8705c1716aa091d30a88a3e1874d56e99fbf1487cbc65aff970a61a04b1ca14834a43f5de4533ebddb5cc8912ce59144191c1204e64559fe8253a0e49e65489cb911cbb270cae0d132689ce11c2c52ab2dedbcf91107230f06e31ab7213826fd204e0a06c2e052e3d2f7c05b818ac79765329c953ef2427714fb5bf92768916015b5ebd9fa54d6ba10da5864e24914e80772eaf6e2e18b651f160bc9158b2a5cafca65"; + + string constant pathId_0p1_zap = "ac4d7cd20a8ce8b236199ea205a4ac16"; + uint256 constant blockNumber_0p1_zap = 276_777_200; + uint256 constant inAmount_0p1_zap = 100_000_000_000_000_000; + uint256 constant outAmount_0p1_zap = 315_036_729; + uint256 constant inValue_0p1_zap = 314_980_640_462_289_240_064; + uint256 constant outValue_0p1_zap = 315_163_462_309_693_685_760; + int256 constant percentDiff_0p1_zap = 58_042_248_925_559_648; + bytes constant swapData_0p1_zap = + hex"83bd37f9000b000a08016345785d8a00000412c7143900c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000015615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f00000000150607180109c41eca030100010102011e010e3f04d90b01000302010109194cbf0b010004020101126306ff0b02000502010110e5c3b60b0300060201010e127de50b040007020101f4a205c80b0501080201000b06000902010200000c0700000a0b0052688295413b32626d226a205b95cdb337de860002000000000000000003d1060b06000c0d0005877a6e7c0b00000e0f00040b0300100f00080b00001112000c0b0501131400060b05011516010044050101170b00ff00000000000000bf6cbb1f40a542af50839cad01b0dc1747f11e1882af49447d8a07e3bd95bd0d56f35241523fbab1994eabfd2880b20239c291e8d79e0346e4a885b256839affdf658af80c7909e18bd2b67386da78d97cccba38e2d959fe135e79aebb57ccb27b128358ff96d42dc8e2700abab1f1f82ecf699caa1a20563dd2fdba71282083d440687cce9e4231aaac534e7fcdc35463e3770c2fb992716cd070b63540b9474fc7ac44dae553464d92c049175133239a8705c1ff970a61a04b1ca14834a43f5de4533ebddb5cc8912ce59144191c1204e64559fe8253a0e49e65488d68772af387a003cc74f993822b3b7f082c23bcfa7f8980b0f1e64a2062791cc3b0871572f1f7f081d1cc282e9a097115e59f67b9d81d4d1d00ac51fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9eaf03f385c642b02b5c563640bba7c4fbf96c27df92768916015b5ebd9fa54d6ba10da5864e24914e80772eaf6e2e18b651f160bc9158b2a5cafca65655c1607f8c2e73d5b4ddabce9ba8792b87592b6f97f4df75117a78c1a5a0dbb814af92458539fb45a17cbf5f866bde11c28861a2742764fac0eba4b2f2a2543b76a4166549f7aab2e75bef0aefc5b0f716aa091d30a88a3e1874d56e99fbf1487cbc65a"; + + string constant pathId_1_zap = "12ad4f2ee02dc2f913ceef34b6e087ed"; + uint256 constant blockNumber_1_zap = 276_777_200; + uint256 constant inAmount_1_zap = 1_000_000_000_000_000_000; + uint256 constant outAmount_1_zap = 3_149_233_888; + uint256 constant inValue_1_zap = 3_150_241_760_852_697_939_968; + uint256 constant outValue_1_zap = 3_150_470_370_162_295_439_360; + int256 constant percentDiff_1_zap = 7_256_881_438_081_564; + bytes constant swapData_1_zap = + hex"83bd37f9000b000a080de0b6b3a764000004bbb57ee000c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000015615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f000000001c08062001012c76dd030100010102011e01013d7fe3030100010302011e010127ce4d030100010402011e0101836c7e0b010005020101031da70e0b01000602010102365133490100010702000131ff99ff0b0201080201011d6195b00b02010902010104814f9c0b02010a0201019d53968b0b02010b020101422d01d50b03000c0201012b9e9e790b03000d020101eff09efb0b03000e0201018f4926090b04000f0201000b0500100201020000080b01001112000a0b000013140007d3ce23570b05161617000641041817000a0a02010015190100080b02011a1b00020b02011c1d0000440201011e1f00ff00000000000000000000000000000000000000a6c5c7d189fa4eb5af8ba34e63dcdd3a635d433f82af49447d8a07e3bd95bd0d56f35241523fbab12d17ebadaefc0b99e38c79b015eace06277e8906bf6cbb1f40a542af50839cad01b0dc1747f11e18994eabfd2880b20239c291e8d79e0346e4a885b20d7c4b40018969f81750d0a164c3839a77353efb0be4ac7da6cd4bad60d96fbc6d091e1098afa358b1026b8e7276e7ac75410f1fcbbe21796e8f75266f38e884725a116c9c7fbf208e79fe8828a2595f30afbcf9458c3131a6d051c621e307e6278e41107fcdc35463e3770c2fb992716cd070b63540b9477cccba38e2d959fe135e79aebb57ccb27b12835842161084d0672e1d3f26a9b53e653be2084ff19c389938cf14be379217570d8e4619e51fbdafaa21ff96d42dc8e2700abab1f1f82ecf699caa1a20563dd2fdba71282083d440687cce9e4231aaac534e8d68772af387a003cc74f993822b3b7f082c23bcfa7f8980b0f1e64a2062791cc3b0871572f1f7f0f92768916015b5ebd9fa54d6ba10da5864e24914e80772eaf6e2e18b651f160bc9158b2a5cafca651c34204fcfe5314dcf53be2671c02c35db58b4e3fa4971dc5ad81b4fccaffad0a584d13192b7d2bafd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9e4b2dfc82977dd2dce7e8d37895a6a8f50cbb4fb5d3a1ff2b6bab83b63cd9ad0787074081a52ef34c86eb7b85807020b4548ee05b54bfc956eebbfcdff970a61a04b1ca14834a43f5de4533ebddb5cc8655c1607f8c2e73d5b4ddabce9ba8792b87592b6f97f4df75117a78c1a5a0dbb814af92458539fb4716aa091d30a88a3e1874d56e99fbf1487cbc65a912ce59144191c1204e64559fe8253a0e49e6548"; + + string constant pathId_10_zap = "53e7412067ebe09dea8447d33ae19e3f"; + uint256 constant blockNumber_10_zap = 276_777_141; + uint256 constant inAmount_10_zap = 10_000_000_000_000_000_000; + uint256 constant outAmount_10_zap = 31_489_123_427; + uint256 constant inValue_10_zap = 31_494_593_360_191_079_579_648; + uint256 constant outValue_10_zap = 31_501_986_315_803_172_012_032; + int256 constant percentDiff_10_zap = 23_473_729_371_701_780; + bytes constant swapData_10_zap = + hex"83bd37f9000b000a088ac7230489e80000050754e5e46300c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000015615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f00000000100504120167b95bfb0b010101020101121232590b0101030201010c5500ad0b010104020101490175bb0b0101050201012726aac24401010106020001249b9ba40b02000702010133c436ae0b0200080201000b020009020105d60ce3904b000a0b0c0100055d86e66141000d0b00040b030f0f0b00060a0101000e100100000b0101110c00ff0000000000000000000000000000000000000000000000000000b1026b8e7276e7ac75410f1fcbbe21796e8f752682af49447d8a07e3bd95bd0d56f35241523fbab16f38e884725a116c9c7fbf208e79fe8828a2595f30afbcf9458c3131a6d051c621e307e6278e41107fcdc35463e3770c2fb992716cd070b63540b9471666ea6e83bcc3473111732ef086ba47e6eddeb77cccba38e2d959fe135e79aebb57ccb27b12835842161084d0672e1d3f26a9b53e653be2084ff19c389938cf14be379217570d8e4619e51fbdafaa217f90122bf0700f9e7e1f688fe926940e8839f353fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9ff970a61a04b1ca14834a43f5de4533ebddb5cc8e4b2dfc82977dd2dce7e8d37895a6a8f50cbb4fb1c34204fcfe5314dcf53be2671c02c35db58b4e3fa4971dc5ad81b4fccaffad0a584d13192b7d2ba5d3a1ff2b6bab83b63cd9ad0787074081a52ef34c86eb7b85807020b4548ee05b54bfc956eebbfcd"; + + string constant pathId_100_zap = "c0c9520d699449766ea0ef8bf8878937"; + uint256 constant blockNumber_100_zap = 276_777_242; + uint256 constant inAmount_100_zap = 100_000_000_000_000_000_000; + uint256 constant outAmount_100_zap = 314_770_429_786; + uint256 constant inValue_100_zap = 315_017_977_316_594_486_870_016; + uint256 constant outValue_100_zap = 314_893_174_435_207_241_007_104; + int256 constant percentDiff_100_zap = -39_617_701_329_433_432; + bytes constant swapData_100_zap = + hex"83bd37f9000b000a09056bc75e2d63100000054949c7975a00c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000015615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f00000000260c082a0110e503c828010202030100605c5b0b010204020101002f07b60b0102050201010133e3290b010206020101001c74b60b010207020101001e74990b01020802010100555f8849010201090200010019f6fe0b02000a02010104c2960b0b02000b0201010802951c480200020c0119c7ba930b03000d020101786530340b03000e02010105e2adfb0b03000f020101057fb3ac0b030010020101077824350b0300110201010156ddfc0b03001202010117d19c980b03001302010161c507ff44030001140200010c2676ef0b040015020101a485739d0b0400160201013125f1aa0b040017020101d1ce72930b040018020101130ea0180b0400190201012677ec730b05001a0201010da95c520b06001b02010189b576f50b06001c0201000b07001d02000c17031e1f200005e36c0fdb0b0300210c00040b0300220c000a0b030023240009fef781dd4e0300012526007ffffff108490300012726010e0b03002829010244030000010300060220ff00000000000000000000000000716aa091d30a88a3e1874d56e99fbf1487cbc65a82af49447d8a07e3bd95bd0d56f35241523fbab1912ce59144191c1204e64559fe8253a0e49e6548e51635ae8136abac44906a8f230c2d235e9c195f92c63d0e701caae670c9415d91c474f686298f00c6f780497a95e246eb9449f5e4770916dcd6396a99543bf98ca1830aa20d3eb12c1b9962f8eadc1111d53ec50bc8f54b9357fbfe2a7de034fc00f8b3c09f4ad33a164e29df3c94719ffd5f7b5b057781521aa84ab3fcc4c05cabac24dc3682339887b126c31e54c7a869b9fcbecc14363cf510d1c41fa443ff970a61a04b1ca14834a43f5de4533ebddb5cc8b1026b8e7276e7ac75410f1fcbbe21796e8f7526c6962004f452be9203591991d15f6b388e09e8d06f38e884725a116c9c7fbf208e79fe8828a2595f30afbcf9458c3131a6d051c621e307e6278e4110f3eb87c1f6020982173c908e7eb31aa66c1f0296d9e2a1a61b6e61b275cec326465d417e52c1b95c7fcdc35463e3770c2fb992716cd070b63540b9471666ea6e83bcc3473111732ef086ba47e6eddeb77cccba38e2d959fe135e79aebb57ccb27b128358641c00a822e8b671738d32a431a4fb6074e5c79d42161084d0672e1d3f26a9b53e653be2084ff19c389938cf14be379217570d8e4619e51fbdafaa210bacc7a9717e70ea0da5ac075889bd87d4c8119731fa55e03bad93c7f8affdd2ec616ebfde2460013dd2fdba71282083d440687cce9e4231aaac534e421803da50d3932caa36bd1731d36a0e2af935424bfc22a4da7f31f8a912a79a7e44a822398b439073cb180bf0521828d8849bc8cf2b920918e23032e80772eaf6e2e18b651f160bc9158b2a5cafca65af88d065e77c8cc2239327c5edb3a432268e5831c86eb7b85807020b4548ee05b54bfc956eebbfcd562d29b54d2c57f8620c920415c4dceadd6de2d245fae8d0d2ace73544baab452f9020925afccc75da10009cbd5d07dd0cecc66161fc93d7c9000da1713e1346d585a1dccadc093210e33cd6bc8cf3d1fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9fc43aaf89a71acaa644842ee4219e8eb776574270e4831319a50228b9e450861297ab92dee15b44f2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"; + + // // Swap Data for Attacker address + string pathId_1000_attacker = "1732b485d29d23688d3dd3a7bc58dad7"; + uint256 blockNumber_1000_attacker = 277_083_108; + uint256 inAmount_1000_attacker = 1_000_000_000; + uint256 outAmount_1000_attacker = 295_926_890_955_538_240; + uint256 inValue_1000_attacker = 1_000_268_562_541_647_101_952; + uint256 outValue_1000_attacker = 999_659_581_622_791_176_192; + int256 percentDiff_1000_attacker = -60_881_741_330_405_248; + bytes swapData_1000_attacker = + hex"83bd37f9000a0000043b9aca0008041b57f07ee3ff4000c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000012e234DAe75C793f67A35089C9d99245E1C58470b00000000120703120108c553560701000102423a1323c871abc9d89eb06855bf5347048fc4a5000000000000000000000496010f93666f0b0100030101010420125b0b010004010101109a94d90b0100050101010eafd2f90b0100060101010d7bd55f4901000107010001128232ec13010001080101012332900a0b0200090100000b02000a010002280000020b0506ebcced030000010c0d011e055a47fec30b00000e0d01050c9ae12d0b00000f0d010544f8ecbb0b0000100d010449000001110d01000602000401ff0000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831ff970a61a04b1ca14834a43f5de4533ebddb5cc8c86eb7b85807020b4548ee05b54bfc956eebbfcd837823ed246a7e34a59aa96701c6f2de9e96d592562d29b54d2c57f8620c920415c4dceadd6de2d223a0f6c10300adbf43c9f12ef197b063a65a85a4de568fd89b3349a766f45d5ab2a7c0510f476a80c1ad239594c165bd4d8847d281232172a452081802be4f98fc9ee4f612a139d84494cbf6c6c7f97f0e4831319a50228b9e450861297ab92dee15b44f82af49447d8a07e3bd95bd0d56f35241523fbab1dd90a3da9688ccca100dd4da1786677bc459f0692f2a2543b76a4166549f7aab2e75bef0aefc5b0f2f5e87c9312fa29aed5c179e456625d79015299c9bcfe8e7f4f671d483e42f3b1511f9c7c720a3464bfc22a4da7f31f8a912a79a7e44a822398b4390cfa09b20c85933b197e8901226ad0d6daca7f114"; + + string pathId_10_attacker = "b8a5092f2dcded24d6cb15c55ecd4555"; + uint256 blockNumber_10_attacker = 277_090_226; + uint256 inAmount_10_attacker = 10_000_000; + uint256 outAmount_10_attacker = 2_973_448_028_214_745; + uint256 inValue_10_attacker = 10_003_549_861_509_439_488; + uint256 outValue_10_attacker = 10_007_525_102_163_920_896; + int256 percentDiff_10_attacker = 39_738_299_998_660_408; + bytes swapData_10_attacker = + hex"83bd37f9000a000003989680070a9055d17ad5d900c49b0001c3F3a07Ae7D2A125EF81a5950c4d0DD54C740251000000012e234DAe75C793f67A35089C9d99245E1C58470b0000000003010203000b0100010200020600000001ff0000000000000000000000000000007fcdc35463e3770c2fb992716cd070b63540b947af88d065e77c8cc2239327c5edb3a432268e5831"; + +} From 6b2ca51112c28f612000c030532a0ad0cdcc55bb Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 6 Dec 2024 00:08:29 +0900 Subject: [PATCH 129/129] reverted usdc zap out case --- src/Zap.sol | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Zap.sol b/src/Zap.sol index 4d97381..86b01e1 100644 --- a/src/Zap.sol +++ b/src/Zap.sol @@ -492,14 +492,9 @@ contract Zap is Reentrancy, Errors, Flush(msg.sender) { // i.e., # of sETH, # of sUSDe, # of sUSDC (...) _withdraw(_collateralId, _collateralAmount, _accountId); - if (_collateral == USDC) { - unwound = _zapOut(_collateralAmount, _collateralAmount / 1e12); - } else { - // unwrap withdrawn synthetix perp position collateral; - // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) - unwound = - _unwrap(_collateralId, _collateralAmount, _unwrapMinAmountOut); - } + // unwrap withdrawn synthetix perp position collateral; + // i.e., sETH -> WETH, sUSDe -> USDe, sUSDC -> USDC (...) + unwound = _unwrap(_collateralId, _collateralAmount, _unwrapMinAmountOut); // establish total debt now owed to Aave; // i.e., # of USDC