You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Description: Description
In the FM_BC_Bancor_Redeeming_VirtualSupply_v1.init() function it is possible to initialise with a combination of Issuance token decimals and initial issuance supply that will leave the contract in a state where it is impossible to call the buy function without it reverting.
Attack Scenario init() is called with an issuance token having 19 decimals and the virtualIssuanceSupply is set to 1 (in this scenraio it should have been set to at least 10)
Bob tries to make a buy with any deposit value, but the function will revert due to the code at line 400 returning zero:
being called with:
virtualIssuanceSupply = 1
issuanceTokenDecimals = 19
eighteenDecimals = 18
This will return 0 as proven by the following unit test
function testDecimalConversion() public {
// Ensure the inputs are within valid rangesuint amount =1;
uint8 tokenDecimals =19;
uint8 requiredDecimals =18;
// Call the function with the base amountuint resultBase =_convertAmountToRequiredDecimal(
amount,
tokenDecimals,
requiredDecimals
);
// Ensure the result is correctassertTrue(resultBase ==0, "Decimal conversion failed");
}
This Zero value is then passed as the first argument to the call to formula.calculatePurchaseReturn in the following function.
// SPDX-License-Identifier: LGPL-3.0-onlypragma solidity^0.8.0;
import"forge-std/console.sol";
// Internal Dependenciesimport {
E2ETest,
IOrchestratorFactory_v1,
IOrchestrator_v1
} from"test/e2e/E2ETest.sol";
import {ERC20Issuance_v1} from"@fm/bondingCurve/tokens/ERC20Issuance_v1.sol";
// SuTimport {
FM_BC_Bancor_Redeeming_VirtualSupply_v1,
IFM_BC_Bancor_Redeeming_VirtualSupply_v1
} from
"test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.t.sol";
import {IBondingCurveBase_v1} from
"@fm/bondingCurve/interfaces/IBondingCurveBase_v1.sol";
contractBondingCurveFundingManagerE2EPOCChangeIssuanceisE2ETest {
// Module Configurations for the current E2E test. Should be filled during setUp() call.
IOrchestratorFactory_v1.ModuleConfig[] moduleConfigurations;
ERC20Issuance_v1 issuanceToken;
address alice =address(0xA11CE);
uint aliceBuyAmount =200e18;
address bob =address(0x606);
uint bobBuyAmount =100_000e18;
function setUp() publicoverride {
// Setup common E2E frameworksuper.setUp();
// Set Up individual Modules the E2E test is going to use and store their configurations:// NOTE: It's important to store the module configurations in order, since _create_E2E_Orchestrator() will copy from the array.// The order should be:// moduleConfigurations[0] => FundingManager// moduleConfigurations[1] => Authorizer// moduleConfigurations[2] => PaymentProcessor// moduleConfigurations[3:] => Additional Logic Modules// FundingManagersetUpBancorVirtualSupplyBondingCurveFundingManager();
// BancorFormula 'formula' is instantiated in the E2EModuleRegistry
IBondingCurveBase_v1.IssuanceToken memory issuanceToken_properties =
IBondingCurveBase_v1.IssuanceToken({
name: "Bonding Curve Token",
symbol: "BCT",
decimals: 19,
maxSupply: type(uint).max -1
});
address issuanceTokenAdmin =address(this);
IFM_BC_Bancor_Redeeming_VirtualSupply_v1.BondingCurveProperties memory
bc_properties = IFM_BC_Bancor_Redeeming_VirtualSupply_v1
.BondingCurveProperties({
formula: address(formula),
reserveRatioForBuying: 333_333,
reserveRatioForSelling: 333_333,
buyFee: 0,
sellFee: 0,
buyIsOpen: true,
sellIsOpen: true,
initialIssuanceSupply: 1,
initialCollateralSupply: 3
});
moduleConfigurations.push(
IOrchestratorFactory_v1.ModuleConfig(
bancorVirtualSupplyBondingCurveFundingManagerMetadata,
abi.encode(
issuanceToken_properties,
issuanceTokenAdmin,
bc_properties,
token
)
)
);
// AuthorizersetUpRoleAuthorizer();
moduleConfigurations.push(
IOrchestratorFactory_v1.ModuleConfig(
roleAuthorizerMetadata, abi.encode(address(this))
)
);
// PaymentProcessorsetUpSimplePaymentProcessor();
moduleConfigurations.push(
IOrchestratorFactory_v1.ModuleConfig(
simplePaymentProcessorMetadata, bytes("")
)
);
// Additional Logic ModulessetUpBountyManager();
moduleConfigurations.push(
IOrchestratorFactory_v1.ModuleConfig(
bountyManagerMetadata, bytes("")
)
);
}
function test_e2e_OrchestratorFundManagementPOCChangeIssuance() public {
IOrchestratorFactory_v1.WorkflowConfig memory workflowConfig =
IOrchestratorFactory_v1.WorkflowConfig({
independentUpdates: false,
independentUpdateAdmin: address(0)
});
IOrchestrator_v1 orchestrator =_create_E2E_Orchestrator(workflowConfig, moduleConfigurations);
FM_BC_Bancor_Redeeming_VirtualSupply_v1 fundingManager =FM_BC_Bancor_Redeeming_VirtualSupply_v1(
address(orchestrator.fundingManager())
);
issuanceToken =ERC20Issuance_v1(fundingManager.getIssuanceToken());
token.mint(bob, bobBuyAmount);
uint buf_minAmountOut =
fundingManager.calculatePurchaseReturn(bobBuyAmount);
console.log("buf_minAmountOut: ", buf_minAmountOut);
uint bobTokenBalanceBefore = token.balanceOf(bob);
console.log("Bob token balance before: ", token.balanceOf(bob));
// Bob performs a buy
vm.startPrank(bob);
{
// Approve tokens to fundingmanager.
token.approve(address(fundingManager), bobBuyAmount);
// Deposit tokens, i.e. fund the fundingmanager.
fundingManager.buy(bobBuyAmount, buf_minAmountOut);
// After the deposit, bob received some amount of receipt tokens// from the fundingmanager.assertTrue(issuanceToken.balanceOf(bob) >0);
}
vm.stopPrank();
}
}
Revised Code File (Optional)
Recommendation
Add a check to the init() function to ensure that the virtualIssuanceSupply is initialised to a minimum amount compatibale with the relative token decimals. i.e if Issuance token has 19 Decimals the the virtualIssuanceSupply should be at least 1e1 or 10 (1e(issuance token decimals - 18))
The text was updated successfully, but these errors were encountered:
It is a way too specific of an edge-case and I see no reason for an Orchestrator with a 19 decimal issuance token to set the virtual supply to 1.
But good catch on the edge-case.
I took the initial supply numbers from the existing E2E tests and it is actually perfectly fine to set the initial supply to 1 if the token decimals are the same. As mentioned in my recommendation the initial supply needs to be at least the difference in token decimals. And used 19 decimals in the example just to show that even one decimal place out causes the issue.
Github username: --
Twitter username: rnemes4
Submission hash (on-chain): 0xc4e1d972c648049ac67f27cf0ab33d445e5b01968a8160c3b54ba91721fb017e
Severity: medium
Description:
Description
In the
FM_BC_Bancor_Redeeming_VirtualSupply_v1.init()
function it is possible to initialise with a combination of Issuance token decimals and initial issuance supply that will leave the contract in a state where it is impossible to call thebuy
function without it reverting.Attack Scenario
init()
is called with an issuance token having 19 decimals and the virtualIssuanceSupply is set to 1 (in this scenraio it should have been set to at least 10)Bob tries to make a
buy
with any deposit value, but the function will revert due to the code at line 400 returning zero:FM_BC_Tools._convertAmountToRequiredDecimal( virtualIssuanceSupply, issuanceTokenDecimals, eighteenDecimals ),
being called with:
virtualIssuanceSupply = 1
issuanceTokenDecimals = 19
eighteenDecimals = 18
This will return 0 as proven by the following unit test
This Zero value is then passed as the first argument to the call to
formula.calculatePurchaseReturn
in the following function.calculatePurchaseReturn
will then revert due to the fact_supply
has been rounded down to ZeroAttachments
Add the following test to the E2E test suit, which will revert with
Add a check to the
init()
function to ensure that thevirtualIssuanceSupply
is initialised to a minimum amount compatibale with the relative token decimals. i.e if Issuance token has 19 Decimals the thevirtualIssuanceSupply
should be at least1e1 or 10 (1e(issuance token decimals - 18))
The text was updated successfully, but these errors were encountered: