Platform: Code4rena
Start Date: 18/05/2023
Pot Size: $24,500 USDC
Total HM: 3
Participants: 72
Period: 4 days
Judge: LSDan
Id: 237
League: ETH
Rank: 6/72
Findings: 2
Award: $952.18
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 0xRobocop, 0xnacho, HHK, SpicyMeatball, max10afternoon, rbserver
630.4612 USDC - $630.46
The payParams
function will cause the pay()
function in the terminal (used to call in the JBXBuybackDelegate
contract) to revert, if the user specify a minimum amount of token to mint (other than 0), while the swap pathway is selected. Preventing the user from being able to specify the minimum amount of tokens to receive.
(They can still specify the splippage in the swap, but didPay()
, will mint the tokens instead if such criteria is not met https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/JBXBuybackDelegate.sol#L205).
One of the values returned by payParams
will be set to 0 each time the swap pathway is taken.
This value will later cause the following statement to revert: if (beneficiaryTokenCount < _minReturnedTokens) revert INADEQUATE_TOKEN_COUNT();
(in the pay()
function, from the terminal contract) for any value of _minReturnedTokens !=0
. More in detail:
"If the amount swapped is bigger than the lowest received when minting" , the payParams
function will returun the following values:
return (0, _data.memo, delegateAllocations);
(As can be seen here: https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/JBXBuybackDelegate.sol#L156 , https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/JBXBuybackDelegate.sol#L166 ).
Those values (the 0
in particular) are used by the recordPaymentFrom()
in the JBSingleTokenPaymentTerminalStore3_1.sol
contract. Which calls payParams
to set _weight
:
(_weight, memo, delegateAllocations) = IJBFundingCycleDataSource(fundingCycle.dataSource()).payParams(_data);
and than uses the result to decide what to return:
if (_weight == 0) return (fundingCycle, 0, delegateAllocations, memo);
This operations are invoked by the pay()
function of the JBPayoutRedemptionPaymentTerminal3_1
contract, to set the following parameters:
(_fundingCycle, _tokenCount, _delegateAllocations, _memo) = store.recordPaymentFrom( _payer, _bundledAmount, _projectId, baseWeightCurrency,_beneficiary, _memo, _metadata);
As you might see from the calls above, when the swap pathway is taken _tokenCount
in the previous call will be set to 0
.
In the instructions right below this, the pay function will check if _tokenCount
is greater than 0, to decide weather or not it should mint some tokens to the beneficiary:
if (_tokenCount > 0) beneficiaryTokenCount = IJBController(directory.controllerOf(_projectId)).mintTokensOf( = _projectId, _tokenCount, _beneficiary,'',_preferClaimedTokens,true);
Since _tokenCount == 0
, beneficiaryTokenCount
will keep it's uninitialized vale of 0
and the check right bellow will fail for any value of _minReturnedTokens
diffrent from 0:
if (beneficiaryTokenCount < _minReturnedTokens) revert INADEQUATE_TOKEN_COUNT();
_minReturnedTokens
being the value passed to the pay()
function to specify the minimum amount of tokens to mint.
(https://github.com/jbx-protocol/juice-contracts-v3/blob/12d852f28d372dd44987586f8009c56b0fe247a9/contracts/abstract/JBPayoutRedemptionPaymentTerminal3_1.sol#L1437)
Here is a Forgery test that you can run. It checks for a revert with error: INADEQUATE_TOKEN_COUNT
(on line 171
):
// SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import '../interfaces/external/IWETH9.sol'; import './helpers/TestBaseWorkflowV3.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController3_1.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleStore.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleBallot.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleDataSource.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBOperatable.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayDelegate.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBRedemptionDelegate.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayoutRedemptionPaymentTerminal.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSingleTokenPaymentTerminalStore.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBToken.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBConstants.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBCurrencies.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBFundingCycleMetadataResolver.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBOperations.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/structs/JBFundingCycle.sol'; import '@paulrberg/contracts/math/PRBMath.sol'; import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol'; import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol'; import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol'; import '@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol'; import '@uniswap/v3-core/contracts/libraries/TickMath.sol'; import '@exhausted-pigeon/uniswap-v3-forge-quoter/src/UniswapV3ForgeQuoter.sol'; import '../JBXBuybackDelegate.sol'; import '../mock/MockAllocator.sol'; import 'forge-std/Test.sol'; import{JBPayoutRedemptionPaymentTerminal3_1} from 'node_modules/@jbx-protocol/juice-contracts-v3/contracts/abstract/JBPayoutRedemptionPaymentTerminal3_1.sol'; /** * @notice JBXBuyback fork integration tests, using $jbx v3 */ contract TestIntegrationJBXBuybackDelegate is Test, UniswapV3ForgeQuoter { using JBFundingCycleMetadataResolver for JBFundingCycle; event JBXBuybackDelegate_Swap(uint256 projectId, uint256 amountEth, uint256 amountOut); event JBXBuybackDelegate_Mint(uint256 projectId); event Mint( address indexed holder, uint256 indexed projectId, uint256 amount, bool tokensWereClaimed, bool preferClaimedTokens, address caller ); // Contracts needed IJBFundingCycleStore jbFundingCycleStore; IJBProjects jbProjects; IJBSplitsStore jbSplitsStore; IJBPayoutRedemptionPaymentTerminal3_1 jbEthPaymentTerminal; IJBSingleTokenPaymentTerminalStore jbTerminalStore; IJBController3_1 jbController; IJBTokenStore jbTokenStore; // Structure needed JBProjectMetadata projectMetadata; JBFundingCycleData data; JBFundingCycleMetadata metadata; JBFundAccessConstraints[] fundAccessConstraints; IJBPaymentTerminal[] terminals; JBGroupedSplits[] groupedSplits; JBXBuybackDelegate delegate; IUniswapV3Pool pool; IERC20 jbx = IERC20(0x4554CC10898f92D45378b98D6D6c2dD54c687Fb2); // 0 - 69420*10**18 IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // 1 - 1*10**18 uint256 price = 69420 ether; // sqrtPriceX96 = sqrt(1*10**18 << 192 / 69420*10**18) = 300702666377442711115399168 (?) uint160 sqrtPriceX96 = 300702666377442711115399168; uint256 amountOutForOneEth; function setUp() public { vm.createSelectFork(vm.envString("RPC_MAINNET_URL"), 17239357); // Collect the mainnet deployment addresses jbEthPaymentTerminal = IJBPayoutRedemptionPaymentTerminal3_1( stdJson.readAddress( vm.readFile("node_modules/@jbx-protocol/juice-contracts-v3/deployments/mainnet/JBETHPaymentTerminal3_1.json"), ".address" ) ); vm.label(address(jbEthPaymentTerminal), "jbEthPaymentTerminal3_1"); jbController = IJBController3_1( stdJson.readAddress(vm.readFile("node_modules/@jbx-protocol/juice-contracts-v3/deployments/mainnet/JBController3_1.json"), ".address") ); vm.label(address(jbController), "jbController"); jbTokenStore = jbController.tokenStore(); jbFundingCycleStore = jbController.fundingCycleStore(); jbProjects = jbController.projects(); jbSplitsStore = jbController.splitsStore(); pool = IUniswapV3Pool(IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984).createPool(address(weth), address(jbx), 100)); pool.initialize(sqrtPriceX96); // 1 eth <=> 69420 jbx vm.startPrank(address(123), address(123)); deal(address(weth), address(123), 10000000 ether); deal(address(jbx), address(123), 10000000 ether); // approve: address POSITION_MANAGER = 0xC36442b4a4522E871399CD717aBDD847Ab11FE88; jbx.approve(POSITION_MANAGER, 10000000 ether); weth.approve(POSITION_MANAGER, 10000000 ether); // mint concentrated position INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({ token0: address(jbx), token1: address(weth), fee: 100, tickLower: TickMath.getTickAtSqrtRatio(sqrtPriceX96) - 10 * pool.tickSpacing(), tickUpper: TickMath.getTickAtSqrtRatio(sqrtPriceX96) + 10 * pool.tickSpacing(), amount0Desired: 10000000 ether, amount1Desired: 10000000 ether, amount0Min: 0, amount1Min: 0, recipient: address(123), deadline: block.timestamp }); INonfungiblePositionManager(POSITION_MANAGER).mint(params); vm.stopPrank(); amountOutForOneEth = getAmountOut(pool, 1 ether, address(weth)); delegate = new JBXBuybackDelegate(IERC20(address(jbx)), weth, pool, jbEthPaymentTerminal); vm.label(address(pool), 'uniswapPool'); vm.label(address(weth), '$WETH'); vm.label(address(jbx), '$JBX'); } //THIS TEST WILL PASS IF pay() REVERST WITH "INADEQUATE_TOKEN_COUNT" function test_swapIfQuoteBetter(uint256 _weight) public { _weight = bound(_weight, 0, amountOutForOneEth - (amountOutForOneEth * 500 / 10000) - 1); _reconfigure(1, address(delegate), _weight, 5000); uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); // Build the metadata using the quote at that block bytes memory _metadata = abi.encode( bytes32(0), bytes32(0), amountOutForOneEth, //quote 500 //slippage 500/10000 = 5% ); vm.expectRevert(JBPayoutRedemptionPaymentTerminal3_1.INADEQUATE_TOKEN_COUNT.selector); // Pay the project jbEthPaymentTerminal.pay{value: 1 ether}( 1, 1 ether, address(0), address(123), /* _minReturnedTokens */ 123, /* _preferClaimedTokens */ true, /* _memo */ 'Take my money!', /* _delegateMetadata */ _metadata ); } function _reconfigure(uint256 _projectId, address _delegate, uint256 _weight, uint256 _reservedRate) internal { address _projectOwner = jbProjects.ownerOf(_projectId); JBFundingCycle memory _fundingCycle = jbFundingCycleStore.currentOf(_projectId); metadata = _fundingCycle.expandMetadata(); JBGroupedSplits[] memory _groupedSplits = new JBGroupedSplits[](1); _groupedSplits[0] = JBGroupedSplits({ group: 1, splits: jbSplitsStore.splitsOf( _projectId, _fundingCycle.configuration, /*domain*/ JBSplitsGroups.ETH_PAYOUT /*group*/) }); metadata.useDataSourceForPay = true; metadata.dataSource = _delegate; metadata.reservedRate = _reservedRate; data.weight = _weight; data.duration = 14 days; // reconfigure vm.prank(_projectOwner); jbController.reconfigureFundingCyclesOf( _projectId, data, metadata, block.timestamp, _groupedSplits, fundAccessConstraints, "" ); // Move to next fc vm.warp(block.timestamp + 14 days + 1); } }
The test will fail when _minReturnedTokens
is set to 0, and pass for the other values.
The if (beneficiaryTokenCount < _minReturnedTokens) revert INADEQUATE_TOKEN_COUNT();
check should only be performed when _tokenCount > 0
.
Invalid Validation
#0 - c4-pre-sort
2023-05-24T16:17:05Z
dmvt marked the issue as duplicate of #36
#1 - c4-judge
2023-06-02T14:23:39Z
dmvt marked the issue as satisfactory
🌟 Selected for report: minhquanym
Also found by: 0xStalin, BLACK-PANDA-REACH, Madalad, T1MOH, Udsen, adriro, max10afternoon, rbserver, sces60107
321.7244 USDC - $321.72
If a user sends ether, to didPay()
, to buy more tokens than what is available in the underlying UnicornV3Pool, the difference between the eth used to buy all the available tokens in the pool, and the amount sent, will be locked in the JBXBuybackDelegate
contract. Resulting in a loss of funds for the user.
Since both to project's token and the V3 pool, are controlled by the owner of the project and/or other external factors, no assumptions on the price and/or availability of the token can be made. Therefor an estimation on the amount of eth necessary for this to happen, or on the likelyhood in general, cannot be made.
The didPay(JBDidPayData calldata _data)
functions is invoked by the JBPayoutRedemptionPaymentTerminal3_1
with the following line of code:
_delegateAllocation.delegate.didPay{value: _payableValue}(_data);
(https://github.com/jbx-protocol/juice-contracts-v3/blob/12d852f28d372dd44987586f8009c56b0fe247a9/contracts/abstract/JBPayoutRedemptionPaymentTerminal3_1.sol#L1532).
Where _payableValue
is the eth being send with the pay()
function by the user, to pay for the tokens.
If didPay
ends up calling into the _mint()
function available in the same contract. This eth will be sent back to the terminal as payment for minting the tokens.
If didPay
performs a swap in the V3 pool to acquire the projectTokens, the uniswapV3SwapCallback
function will convert the eth requested by the pool, into wEth, and than transfers it as payment. (https://github.com/code-423n4/2023-05-juicebox/blob/9d0458282511ff269b3b35b5b082b56d5cc08663/juice-buyback/contracts/JBXBuybackDelegate.sol#L231). No other actions will be performed on the value sent by the user along with the pay()
request. Therefor any eth unused by the uniswapV3SwapCallback
function will be locked in the contract.
If the amount of token available in the pool is lesser than the amount requested, uniswap will send all the available tokens to the caller. And the uniswapV3SwapCallback
, will only spend the amount of eth necessary to pay for them. The difference between what is being paid for the swap and what has been sent, will be locked in the contract.
Here is a foundry test that you can run. It will check weather the eth balance of JBXBuybackDelegate
is equal to 0 before the transaction, and greater than zero after the user attempts to buy a large amount of tokens (since no assumption can be made on the availability and price of the tokens can be made, I've used unrealistic numbers in this script for simplicity and clarity).
// SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import '../interfaces/external/IWETH9.sol'; import './helpers/TestBaseWorkflowV3.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController3_1.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleStore.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleBallot.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleDataSource.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBOperatable.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayDelegate.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBRedemptionDelegate.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayoutRedemptionPaymentTerminal.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSingleTokenPaymentTerminalStore.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBToken.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBConstants.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBCurrencies.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBFundingCycleMetadataResolver.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBOperations.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol'; import '@jbx-protocol/juice-contracts-v3/contracts/structs/JBFundingCycle.sol'; import '@paulrberg/contracts/math/PRBMath.sol'; import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol'; import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol'; import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol'; import '@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol'; import '@uniswap/v3-core/contracts/libraries/TickMath.sol'; import '@exhausted-pigeon/uniswap-v3-forge-quoter/src/UniswapV3ForgeQuoter.sol'; import '../JBXBuybackDelegate.sol'; import '../mock/MockAllocator.sol'; import 'forge-std/Test.sol'; /** * @notice JBXBuyback fork integration tests, using $jbx v3 */ contract TestIntegrationJBXBuybackDelegate is Test, UniswapV3ForgeQuoter { using JBFundingCycleMetadataResolver for JBFundingCycle; event JBXBuybackDelegate_Swap(uint256 projectId, uint256 amountEth, uint256 amountOut); event JBXBuybackDelegate_Mint(uint256 projectId); event Mint( address indexed holder, uint256 indexed projectId, uint256 amount, bool tokensWereClaimed, bool preferClaimedTokens, address caller ); // Contracts needed IJBFundingCycleStore jbFundingCycleStore; IJBProjects jbProjects; IJBSplitsStore jbSplitsStore; IJBPayoutRedemptionPaymentTerminal3_1 jbEthPaymentTerminal; IJBSingleTokenPaymentTerminalStore jbTerminalStore; IJBController3_1 jbController; IJBTokenStore jbTokenStore; // Structure needed JBProjectMetadata projectMetadata; JBFundingCycleData data; JBFundingCycleMetadata metadata; JBFundAccessConstraints[] fundAccessConstraints; IJBPaymentTerminal[] terminals; JBGroupedSplits[] groupedSplits; JBXBuybackDelegate delegate; IUniswapV3Pool pool; IERC20 jbx = IERC20(0x4554CC10898f92D45378b98D6D6c2dD54c687Fb2); // 0 - 69420*10**18 IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // 1 - 1*10**18 uint256 price = 69420 ether; //Q64.96 = 1 uint160 sqrtPriceX96 = 79228162514264337593543950336; uint256 amountOutForOneEth; function setUp() public { vm.createSelectFork(vm.envString("RPC_MAINNET_URL"), 17239357); // Collect the mainnet deployment addresses jbEthPaymentTerminal = IJBPayoutRedemptionPaymentTerminal3_1( stdJson.readAddress( vm.readFile("node_modules/@jbx-protocol/juice-contracts-v3/deployments/mainnet/JBETHPaymentTerminal3_1.json"), ".address" ) ); vm.label(address(jbEthPaymentTerminal), "jbEthPaymentTerminal3_1"); jbController = IJBController3_1( stdJson.readAddress(vm.readFile("node_modules/@jbx-protocol/juice-contracts-v3/deployments/mainnet/JBController3_1.json"), ".address") ); vm.label(address(jbController), "jbController"); jbTokenStore = jbController.tokenStore(); jbFundingCycleStore = jbController.fundingCycleStore(); jbProjects = jbController.projects(); jbSplitsStore = jbController.splitsStore(); pool = IUniswapV3Pool(IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984).createPool(address(weth), address(jbx), 100)); pool.initialize(sqrtPriceX96); // 1 eth <=> 69420 jbx vm.startPrank(address(123), address(123)); deal(address(weth), address(123), 10000000 ether); deal(address(jbx), address(123), 10000000 ether); // approve: address POSITION_MANAGER = 0xC36442b4a4522E871399CD717aBDD847Ab11FE88; jbx.approve(POSITION_MANAGER, 10000000 ether); weth.approve(POSITION_MANAGER, 10000000 ether); // mint concentrated position INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({ token0: address(jbx), token1: address(weth), fee: 100, tickLower: TickMath.getTickAtSqrtRatio(sqrtPriceX96) - 10 * pool.tickSpacing(), tickUpper: TickMath.getTickAtSqrtRatio(sqrtPriceX96) + 10 * pool.tickSpacing(), amount0Desired: 10000000 ether, amount1Desired: 10000000 ether, amount0Min: 0, amount1Min: 0, recipient: address(123), deadline: block.timestamp }); INonfungiblePositionManager(POSITION_MANAGER).mint(params); vm.stopPrank(); amountOutForOneEth = getAmountOut(pool, 1 ether, address(weth)); delegate = new JBXBuybackDelegate(IERC20(address(jbx)), weth, pool, jbEthPaymentTerminal); vm.label(address(pool), 'uniswapPool'); vm.label(address(weth), '$WETH'); vm.label(address(jbx), '$JBX'); } function test_lockedEth() public { uint256 _weight = 1; // Reconfigure with a weight smaller than the quote, slippage included _weight = bound(_weight, 0, amountOutForOneEth - (amountOutForOneEth * 500 / 10000) - 1); _reconfigure(1, address(delegate), _weight, 5000); uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); // Build the metadata using the quote at that block bytes memory _metadata = abi.encode( bytes32(0), bytes32(0), amountOutForOneEth, //quote 500 //slippage 500/10000 = 5% ); uint256 locked_eth = address(delegate).balance; console.log('Balance of delegate contract before swap:'); console.log(locked_eth); assertEq(locked_eth, 0); // Pay the project jbEthPaymentTerminal.pay{value: 300000000000000000000000000}( 1, 300000000000000000000000000, address(0), address(123), /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ true, /* _memo */ 'Take my money!', /* _delegateMetadata */ _metadata ); uint256 c = jbx.balanceOf(address(pool)); console.log("Balance of projectTokens left in V3 pool after the swap: "); console.log(c); locked_eth = address(delegate).balance; console.log('Balance of delegate contract after swap:'); console.log(locked_eth); assertGt(locked_eth, 0); } function _reconfigure(uint256 _projectId, address _delegate, uint256 _weight, uint256 _reservedRate) internal { address _projectOwner = jbProjects.ownerOf(_projectId); JBFundingCycle memory _fundingCycle = jbFundingCycleStore.currentOf(_projectId); metadata = _fundingCycle.expandMetadata(); JBGroupedSplits[] memory _groupedSplits = new JBGroupedSplits[](1); _groupedSplits[0] = JBGroupedSplits({ group: 1, splits: jbSplitsStore.splitsOf( _projectId, _fundingCycle.configuration, /*domain*/ JBSplitsGroups.ETH_PAYOUT /*group*/) }); metadata.useDataSourceForPay = true; metadata.dataSource = _delegate; metadata.reservedRate = _reservedRate; data.weight = _weight; data.duration = 14 days; // reconfigure vm.prank(_projectOwner); jbController.reconfigureFundingCyclesOf( _projectId, data, metadata, block.timestamp, _groupedSplits, fundAccessConstraints, "" ); // Move to next fc vm.warp(block.timestamp + 14 days + 1); } }
If you run this test with the -vvv
option it will log the following content to the console:
[PASS] test_lockedEth() (gas: 26476658) Logs: Bound Result 1 Balance of delegate contract before swap: 0 Balance of projectTokens left in V3 pool after the swap: 1 Balance of delegate contract after swap: 289993998399739968996799679 Test result: ok. 1 passed; 0 failed; finished in 550.03ms
Although the user can adjust the splippage, the contract should send back any leftover eth after it finishes all it's operations.
ETH-Transfer
#0 - c4-pre-sort
2023-05-25T12:50:33Z
dmvt marked the issue as duplicate of #42
#1 - c4-judge
2023-06-02T14:26:00Z
dmvt changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-06-02T14:45:33Z
dmvt marked the issue as satisfactory