Karak Pro League
Findings & Analysis Report
2024-07-16
Table of contents
- Summary
- Scope
- Severity Criteria
-
- L-01 Hardcoded gas cost
- L-02 Low-level call against an EOA (DSS) will always succeed
- L-03
slashablePercentageWad
can exceed 100% - L-04 Standard implementation can be set to reserved address (
address(1)
) - L-05
assetSlashingHandlers
can be set toaddress(0)
- L-06 Incorrect check for low-level calls in
callHookIfInterfaceImplemented()
- L-07
slashablePercentageWad
can be 0 - L-08
registerOperatorToDSS()
can be called repeatedly - L-09
implementation(address(0))
should not returnself.vaultImpl
- L-10 Risks of distributing rewards directly to the vault as underlying assets
- Disclosures
Overview
About C4
Code4rena (C4) is an open organization consisting of security researchers, auditors, developers, and individuals with domain expertise in smart contracts.
A C4 Pro League Audit is an event where elite tier Code4rena contributors, commonly referred to as wardens, reviews, audits and analyzes smart contract logic in exchange for a bounty provided by sponsoring projects.
During the Pro League audit outlined in this document, C4 conducted an analysis of the Karak smart contract system written in Solidity. The audit took place between June 10 - July 3, 2024.
Wardens
2 Wardens contributed to Karak:
Final report assembled by bytes032 and thebrittfactor.
Summary
The C4 Pro League analysis yielded an aggregated total of 3 HIGH severity vulnerabilities.
Additionally, C4 Pro League analysis included 10 findings with a risk rating of LOW severity or non-critical and 4 INFORMATIONAL findings.
Scope
The source code was delivered to Code4rena in a private Git repository.
Severity Criteria
C4 assesses the severity of disclosed vulnerabilities based on three primary risk categories: high, medium, and low/non-critical.
High-level considerations for vulnerabilities span the following key areas when conducting assessments:
- Malicious Input Handling
- Escalation of privileges
- Arithmetic
- Gas use
For more information regarding the severity criteria referenced throughout the submission review process, please refer to the documentation provided on the C4 website, specifically our section on Severity Categorization.
High Risk Findings (3)
[H-01] DSS can slash more assets than are allowed against a vault within a single slashing event
Lines of Code
SlasherLib.sol#L56
Description
Assume that the maxSlashingWad
is set to 5%. This means that for each vault of the operator, the DSS should only be allowed to slash up to 5% in a single slashing event.
However, malicious DSS could bypass the 5% restriction by setting the slashingRequest
to contain [VaultA=5%, VaultA=5%, VaultA=5%]
, which effectively allows the malicious DSS to slash 15% on VaultA
in a single slashing event.
Recommendation
Ensure the slashingRequest.vault
does not contain duplicates.
Karak
Fixed in PR 279.
Code4rena Pro League
Fixed.
[H-02] Malicious operators can bypass checks in DSS Hooks
Lines of Code
- HookLib.sol#L44-L57
- Operator.sol#L125-L138
- Operator.sol#L61-L67
Description
When operators register and stake into the DSS, ignoreFailure
of callHookIfInterfaceImplemented()
is false
, which means that the DSS can check for operators in the Hook and reject operators that do not meet the requirements.
function registerOperatorToDSS(State storage self, IDSS dss, address operator, bytes memory registrationHookData)
external
{
if (self.dssMap.length() == Constants.MAX_DSS_PER_OPERATOR) revert MaxDSSCapacityReached();
self.dssMap.set(address(dss), 1); // Set a non zero value for dss
HookLib.callHookIfInterfaceImplemented(
dss,
abi.encodeWithSelector(dss.registrationHook.selector, operator, registrationHookData),
dss.registrationHook.selector,
false,
Constants.DEFAULT_HOOK_GAS
);
}
In callHookIfInterfaceImplemented()
, a low-level call is made to check whether DSS implements the supportsInterface()
interface, and then call the specific Hook.
function callHookIfInterfaceImplemented(
IERC165 dss,
bytes memory data,
bytes4 interfaceId,
bool ignoreFailure,
uint256 gas
) internal returns (bool) {
(bool success,) = address(dss).call{gas: Math.min(Constants.SUPPORTS_INTERFACE_GAS_LIMIT, gasleft())}(
abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId)
);
if (!success) {
emit InterfaceNotSupported();
return false;
}
return callHook(address(dss), data, ignoreFailure, gas);
}
The problem here is that even if the DSS implements the supportsInterface()
interface and the corresponding Hook, a malicious operator can use a very low GAS to execute the low-level call so that the low-level call fails due to OOG(out-of-gas). Since the low-level call will only forward 63/64 of the gas, the remaining 1/64 is enough to execute the logic when success
is false.
When callHookIfInterfaceImplemented()
returns false
, the protocol doesn’t check for it and assumes that the DSS doesn’t implement any Hooks.
function registerOperatorToDSS(State storage self, IDSS dss, address operator, bytes memory registrationHookData)
external
{
if (self.dssMap.length() == Constants.MAX_DSS_PER_OPERATOR) revert MaxDSSCapacityReached();
self.dssMap.set(address(dss), 1); // Set a non zero value for dss
HookLib.callHookIfInterfaceImplemented(
dss,
abi.encodeWithSelector(dss.registrationHook.selector, operator, registrationHookData),
dss.registrationHook.selector,
false,
Constants.DEFAULT_HOOK_GAS
);
}
A malicious operator can exploit this issue to bypass the checks in registrationHook()
and requestUpdateStakeHook()
.
Aince finalizeSlashing()
and finalizeUpdateVaultStakeInDSS()
can be called by anyone, a malicious user can exploit this issue to intentionally not execute DSS’s finishUpdateStakeHook()
and finishSlashingHook()
.
Recommendation
It is recommended to require gasleft()
to be greater than SUPPORTS_INTERFACE_GAS_LIMIT
in callHookIfInterfaceImplemented()
.
function callHookIfInterfaceImplemented(
IERC165 dss,
bytes memory data,
bytes4 interfaceId,
bool ignoreFailure,
uint256 gas
) internal returns (bool) {
+ if (gasleft() < Constants.SUPPORTS_INTERFACE_GAS_LIMIT ) revert NotEnoughGas();
(bool success,) = address(dss).call{gas: Math.min(Constants.SUPPORTS_INTERFACE_GAS_LIMIT, gasleft())}(
abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId)
);
Karak
Fixed in PR 278.
Code4rena Pro League
The fix implements the recommendation.
[H-03] validateAndUpdateVaultStakeInDSS()
forwards incorrect operator
address to finishUpdateStakeHook()
Lines of Code
- Operator.sol#L82-L88
- IDSS.sol#L15-L16
Description
In IDSS interfaces, the parameter of finishUpdateStakeHook()
is operator
.
function finishUpdateStakeHook(address operator) external;
However, in validateAndUpdateVaultStakeInDSS()
, the forwarded parameter is msg.sender
.
HookLib.callHookIfInterfaceImplemented(
dss,
abi.encodeWithSelector(dss.finishUpdateStakeHook.selector, msg.sender),
dss.finishUpdateStakeHook.selector,
true,
Constants.DEFAULT_HOOK_GAS
);
Since anyone can call finalizeUpdateVaultStakeInDSS()
, this causes the caller address to be treated as the operator
in DSS’s finishUpdateStakeHook()
to execute the associated logic, which could involve something like registering the address to receive rewards.
/// @notice Allows anyone to finish the queued request for an operator to update assets delegated to a DSS
/// @dev Only operator can finish their queued request valid only after a
/// minimum delay of `Constants.MIN_STAKE_UPDATE_DELAY` after starting the request
function finalizeUpdateVaultStakeInDSS(Operator.QueuedStakeUpdate memory queuedStake, address operator)
Recommendation
It is recommended to add the operator
address in QueuedStakeUpdate
structure and forward the operator
address to finishUpdateStakeHook()
in validateAndUpdateVaultStakeInDSS()
.
Karak
Fixed in PR 280.
Code4rena Pro League
The fix implements the recommendation.
Low Risk Findings (10)
[L-01] Hardcoded gas cost
Lines of Code
Constants.sol#L23
Description
There have been multiple instances where the gas costs for certain operations have been changed through EIPs. For instance, EIP-1884 (SLOAD 200 -> 800, BALANCE 400 -> 700
) and EIP-2929 (SLOAD 800 -> 2100
).
It was found that the gas cost is hardcoded. Thus, the hook might revert due to insufficient gas if the gas cost changes in the future.
File: Constants.sol
23: uint256 public constant DEFAULT_HOOK_GAS = 500_000;
24: uint256 public constant HOOK_GAS_BUFFER = 40_000;
25: uint256 public constant SUPPORTS_INTERFACE_GAS_LIMIT = 20_000;
For instance, assume the DSS’s hook carries out 𝑋 number of operations and consumes up to 499,990 gas. The protocol passes 500,000 (DEFAULT_HOOK_GAS
) gas to the hook. All is good, as the DSS’s hook consumes below the limit.
Assume that at some point in the future, some EIPs are implemented, resulting in the gas cost of some operations increasing. Thus, for DSS’s hook to perform same 𝑋 number of operations, the gas to be consumed becomes 500,100.
Since the protocol only passes 500,000 gas (hardcoded) to the DSS, the DSS’s hook execution will always revert due to insufficient gas.
Recommendation
Consider allowing the gas limit to be updatable by the protocol.
Karak
Fixed in PR 283.
Code4rena Pro League
Fixed.
[L-02] Low-level call against an EOA (DSS) will always succeed
Lines of Code
HookLib.sol#L51
Description
The DSS can be a smart contract or EOA.
Assume DSS is an EOA. When performing a low-level call against an EOA, the call will always succeed because EOAs do not have any code associated with them. The call will simply return without executing any contract code. In this case, the success variable will be set to true, indicating that the call itself was successful.
The program will wrongly conclude that the DSS (an EOA) supported the interface, implemented the necessary function, and proceeded to call the hook against the DSS.
File: HookLib.sol
44: function callHookIfInterfaceImplemented(
45: IERC165 dss,
46: bytes memory data,
47: bytes4 interfaceId,
48: bool ignoreFailure,
49: uint256 gas
50: ) internal returns (bool) {
51: (bool success,) = address(dss).call{gas: Math.min(Constants.SUPPORTS_INTERFACE_GAS_LIMIT, gasleft())}(
52: abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId)
53: );
54: if (!success) {
55: emit InterfaceNotSupported();
56: return false;
57: }
58: return callHook(address(dss), data, ignoreFailure, gas);
59: }
Recommendation
Consider checking if the DSS is an EOA or smart contract. The program can exit the function immediately if it is not a smart contract.
Karak
Fixed in PR 284.
Code4rena Pro League
Fixed.
[L-03] slashablePercentageWad
can exceed 100%
Lines of Code
Core.sol#L266
Description
The slashablePercentageWad
should never exceed 100%. However, it was found that the DSS can set it to a percentage beyond 100% via the Core.setDssSlashablePercentageWad
function.
File: Core.sol
266: function setDssSlashablePercentageWad(uint256 slashablePercentageWad) external {
267: CoreLib.Storage storage self = _self();
268: uint256 currentSlashablePercentageWad = self.dssSlashablePercentageWad[IDSS(msg.sender)];
269: if (currentSlashablePercentageWad != 0) revert SlashingPercentAlreadySet();
270: self.dssSlashablePercentageWad[IDSS(msg.sender)] = slashablePercentageWad;
271: }
Recommendation
To prevent any potential edge cases, ensure the DSS cannot set its slashable percentage to more than 100%.
+ require(slashablePercentageWad <= Constants.MAX_SLASHING_PERCENT_WAD)
In addition, consider disallowing DSS to set its slashable percentage to zero (if applicable).
Karak
Fixed in PR 287.
Code4rena Pro League
Fixed.
[L-04] Standard implementation can be set to reserved address (address(1)
)
Lines of Code
Core.sol#L171
Description
address(1)
is a reserved address for default implementation within the protocol. The protocol should not allow anyone to set the standard implementation address to address(1)
.
File: Core.sol
171: function changeStandardImplementation(address newVaultImpl) external onlyOwner { // @audit-ok
172: if (newVaultImpl == address(0)) revert ZeroAddress();
173: CoreLib.Storage storage self = _self();
174: self.vaultImpl = newVaultImpl;
175: emit UpgradedAllVaults(newVaultImpl);
176: }
Recommendation
To avoid any unexpected error or mistake, consider adding the following check since address(1)
is reserved for default implementation.
+ if (newVaultImpl == Constants.DEFAULT_VAULT_IMPLEMENTATION_FLAG) revert ReservedAddress();
Karak
Fixed in PR 287.
Code4rena Pro League
Fixed.
[L-05] assetSlashingHandlers
can be set to address(0)
Lines of Code
CoreLib.sol#L51-L58
Description
allowlistAssets()
allows setting assetSlashingHandlers[asset]
to address(0)
to invalidate the slashingHandler
.
function allowlistAssets(Storage storage self, address[] memory assets, address[] memory slashingHandlers)
external
{
if (assets.length != slashingHandlers.length) revert LengthsDontMatch();
for (uint256 i = 0; i < assets.length; i++) {
self.assetSlashingHandlers[assets[i]] = slashingHandlers[i];
}
}
However, this invalidation may cause verifyAndFinalizeSlashing()
to fail, which could cause the DOS.
for (uint256 i = 0; i < queuedSlashing.vaults.length; i++) {
IVault(queuedSlashing.vaults[i]).slashAssets(
queuedSlashing.earmarkedStakes[i], self.assetSlashingHandlers[IVault(queuedSlashing.vaults[i]).asset()]
);
}
Recommendation
It is recommended to add the following check to prevent assetSlashingHandlers
from being set to address(0)
.
function allowlistAssets(Storage storage self, address[] memory assets, address[] memory slashingHandlers)
external
{
if (assets.length != slashingHandlers.length) revert LengthsDontMatch();
for (uint256 i = 0; i < assets.length; i++) {
+ if (slashingHandlers[i] == address(0)) revert ZeroAddress();
self.assetSlashingHandlers[assets[i]] = slashingHandlers[i];
}
}
Karak
Fixed in PR 284.
Code4rena Pro League
The fix implements the recommendation.
[L-06] Incorrect check for low-level calls in callHookIfInterfaceImplemented()
Lines of Code
HookLib.sol#L44-L57
Description
In callHookIfInterfaceImplemented()
, only the calling status is checked without checking the return value.
function callHookIfInterfaceImplemented(
IERC165 dss,
bytes memory data,
bytes4 interfaceId,
bool ignoreFailure,
uint256 gas
) internal returns (bool) {
(bool success,) = address(dss).call{gas: Math.min(Constants.SUPPORTS_INTERFACE_GAS_LIMIT, gasleft())}(
abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId)
);
if (!success) {
emit InterfaceNotSupported();
return false;
}
When the contract does not support this interface, success
is generally true, but the return value is false.
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return supportedInterface[interfaceId];
}
Recommendation
It is recommended to add a condition in the following check that success
is true
and the return value is fasle
.
if (!success) {
emit InterfaceNotSupported();
return false;
}
Karak
Fixed in PR 278.
Code4rena Pro League
The fix implements the recommendation.
[L-07] slashablePercentageWad
can be 0
Lines of Code
- SlasherLib.sol#L62-L76
- Vault.sol#L196-L205
- SlashingHandler.sol#L37-L38
Description
When the slashPercentagesWad
provided by the DSS is 0, transferAmount
will be 0 in slashAssets()
.
function slashAssets(uint256 totalAssetsToSlash, address slashingHandler)
external
onlyCore
returns (uint256 transferAmount)
{
transferAmount = Math.min(totalAssets(), totalAssetsToSlash);
// Approve to the handler and then call the handler which will draw the funds
SafeTransferLib.safeApproveWithRetry(asset(), slashingHandler, transferAmount);
ISlashingHandler(slashingHandler).handleSlashing(IERC20(asset()), transferAmount);
Then in handleSlashing()
, when the amount is 0, it throws the ZeroAmount()
error, which will cause the whole slashing to get stuck.
function handleSlashing(IERC20 token, uint256 amount) external {
if (amount == 0) revert ZeroAmount();
Recommendation
It is recommended to require slashPercentagesWad
to be greater than 0 in requestSlashing()
.
Karak
Fixed in PR 284.
Code4rena Pro League
The fix implements the recommendation.
[L-08] registerOperatorToDSS()
can be called repeatedly
Lines of Code
Core.sol#L89-L101
Description
A registered operator can repeatedly call registerOperatorToDSS()
, which emits repeated RegistedOperatorToDss()
events and makes repeated calls to the DSS’s registrationHook()
, resulting in potential side effects.
function registerOperatorToDSS(IDSS dss, bytes memory registrationHookData)
external
whenFunctionNotPaused(Constants.PAUSE_CORE_REGISTER_TO_DSS)
nonReentrant
{
CoreLib.Storage storage self = _self();
address operator = msg.sender;
self.operatorState[operator].registerOperatorToDSS(dss, operator, registrationHookData);
emit RegistedOperatorToDss(operator, address(dss));
}
Recommendation
It is recommended to add the following check to prevent repeated calls to registerOperatorToDSS()
:
function registerOperatorToDSS(IDSS dss, bytes memory registrationHookData)
external
whenFunctionNotPaused(Constants.PAUSE_CORE_REGISTER_TO_DSS)
nonReentrant
{
+ require(!isOperatorRegisteredToDSS(msg.sender, dss));
CoreLib.Storage storage self = _self();
address operator = msg.sender;
self.operatorState[operator].registerOperatorToDSS(dss, operator, registrationHookData);
Karak
Fixed in PR 284.
Code4rena Pro League
The fix implements the recommendation.
[L-09] implementation(address(0))
should not return self.vaultImpl
Lines of Code
Core.sol#L308-L316
Description
When deploying a Vault, if implementation
is address(0)
, implementation
is replaced with DEFAULT_VAULT_IMPLEMENTATION_FLAG
and assigned to vaultToImplMap
, which makes valid vaultToImplMap
will not be address(0)
.
if (implementation == address(0)) {
// Allows us to change all the standard vaults to a new implementation
implementation = Constants.DEFAULT_VAULT_IMPLEMENTATION_FLAG;
}
In changeImplementationForVault()
, a vaultToImplMap
of address(0)
is considered invalid.
if (self.vaultToImplMap[vault] == address(0)) revert VaultNotAChildVault();
However, in implementation()
, address(0)
is considered valid and self.vaultImpl
is returned.
This would make the implementation()
of any non-Vault address be self.vaultImpl
, which might have some side effects out of scope.
Recommendation
It is recommended to make implementation(address(0))
return address(0)
.
function implementation(address vault) public view returns (address) {
CoreLib.Storage storage self = _self();
address vaultImplOverride = self.vaultToImplMap[vault];
- if (vaultImplOverride == Constants.DEFAULT_VAULT_IMPLEMENTATION_FLAG || vaultImplOverride == address(0)) {
+ if (vaultImplOverride == Constants.DEFAULT_VAULT_IMPLEMENTATION_FLAG) {
return self.vaultImpl;
}
return vaultImplOverride;
}
Karak
Fixed in PR 287.
Code4rena Pro League
The fix implements the recommendation.
[L-10] Risks of distributing rewards directly to the vault as underlying assets
Lines of Code
Vault.sol
Description
There are multiple instances where the share price in the vault could increase, allowing users to front-run it, mint a large number of shares, and claim a majority of the upside from the price increase. As a result, the stakers will receive fewer rewards than expected. This issue occurs if the rewards are distributed directly to the vault in underlying assets.
Instance 1
If an operator or DSS distributes a large number of rewards at once to the vault in underlying assets, users can front-run the transaction and mint a large number of shares, claiming most of the rewards. As a result, the stakers will receive fewer rewards than expected.
Instance 2
The share price used to determine the number of assets the user is entitled to during a withdrawal is the minimum of startTimeSharePrice
and endTimeSharePrice
. If the endTimeSharePrice
is larger than startTimeSharePrice
at the point of time of the withdrawal’s finalization, the lower share price (startTimeSharePrice
) will be used, and the “excess” assets to be re-distributed even to all remaining shares or existing users in the vault. This resulted in a sudden increase in the share price.
Similarly, users could front-run it, mint a large number of shares, and claim a majority of the upside from the price increase. As a result, the stakers will receive fewer rewards than expected.
Recommendation
It is not recommended that rewards (especially huge ones) be distributed directly into the vault as underlying assets since the vault code is not really designed for reward distribution, which often requires sophisticated logic (e.g., accruing rewards proportional to the duration of staking) for fairer reward distribution (e.g., SNX reward staking contract).
However, if one still decides to proceed to transfer rewards as underlying assets to the vault directly, the risk should be adequately mitigated with some measures to de-incentivize the attacker or raise the barrier of such an attack. These measures include, but are not limited to, the following:
- Prevent flash-loan - Already in place.
- Prevent the user from being able to withdraw immediately (so that the attacker will also incur the risk of downside - being slashed while in the withdrawal queue) - Already in place.
- Charging a deposit/withdrawal fee to make it less likely to be profitable in most cases.
In addition, consider documenting the risks of directly transferring rewards as underlying assets to the vault so that the respective parties are aware of them.
Karak
Fixed in PR 299.
Code4rena Pro League
- Instance 1: Closed. Noted from the protocol team that the DSS/Operator’s SOP will mention refraining from using the vaults for rewards distribution.
- Instance 2: Closed. Fixed in PR 299. The root cause of this instance is that a disproportional amount of assets is transferred out of the vault when the
finishRedeem
is executed, as the internal formula chooses the minimum between the previous and current assets. This resulted in a sudden surge in share price that malicious users can exploit. However, the fix has updated the formula to return a proportional amount of assets to the withdrawer, thus remediating this instance.
Informational Findings (4)
[I-01] Renaming of function
Lines of Code
CoreLib.sol#L43
Description
The name of the initOrUpdate
function suggests that it is used to initialize state variables and allow their updates after initialization. However, this function is only used during initialization and is not used to update state variables after initialization.
File: CoreLib.sol
43: function initOrUpdate(Storage storage self, address _vaultImpl, address _vetoCommittee) internal { // @audit-ok
Recommendation
Consider renaming it to init
instead of initOrUpdate
to reflect the actual use case.
Karak
Fixed in PR 292.
Code4rena Pro League
Fixed.
[I-02] Unused functions
Lines of Code
- Pauser.sol#L43
- Pauser.sol#L53
Description
Instance 1: The Pauser.whenNotPaused
modifier is not used anywhere in the codebase.
Instance 2: The Pauser.paused
function is not being used anywhere in the codebase.
In addition, the condition here is also incorrect. During initialization, the _getPauserStorage()._paused
is set to 0, meaning the system is unpaused. However, if someone calls the paused()
function, it will return true, meaning the system is paused, thus contradicting the actual state of the system.
Recommendation
Consider removing the unused functions if they are not required.
Karak
Fixed in PR 292.
Code4rena Pro League
Fixed.
[I-03] Ambiguity in the DSSs slashable percentage returned from Core.getDssSlashablePercentageWad
function
Lines of Code
Core.sol#L354
Description
Within the SlasherLib.validate
function, if the dssSlashablePercentageWad
is not initialized (equal to zero), the slashable percentage is 100%.
uint256 maxSlashingWad = self.dssSlashablePercentageWad[dss] == 0
? Constants.MAX_SLASHING_PERCENT_WAD
: self.dssSlashablePercentageWad[dss];
This might cause some confusion for users who rely on the getDssSlashablePercentageWad
function to determine a DSS’s slashable percentage. When this function returns zero, it is unclear whether the DSS’s slashable percentage is 0% or 100%. Users might think that the DSS’s slashable percentage is 0%, while, in fact, it is 100%.
Recommendation
Consider having a variable that keeps track of whether the dssSlashablePercentageWad
has already been initialized so it can be used within the SlasherLib.validate
function to only return MAX_SLASHING_PERCENT_WAD
(100%) if it is uninitialized.
Alternatively, document this behavior in the NatSpec of this function.
Karak
Fixed in PR 287.
Code4rena Pro League
Fixed.
[I-04] Lack of function to get leverage of Vault
Lines of Code
- Operator.sol#L18-L19
- Operator.sol#L103-L105
Description
When the operator stakes the Vault to multiple DSSs, the leverage of the Vault will increase, and DSS can reject stake of Vault with too high leverage, but currently it is difficult for DSS to get the leverage of a Vault through simple operations (it needs to read storage through extSloads()
).
Recommendation
It is recommended to first implement a method to read dssMap
.
+ function getDssMap(State storage self) internal view returns (address[] memory) {
+ return self.dssMap.keys();
+ }
Then for a given Vault, iterate through dssMap
and call isVaultStakeToDSS()
to get the amount of DSS staked by the Vault.
Karak
Fixed in PR 291.
Code4rena Pro League
The fix implements the recommendation.
Disclosures
C4 is an open organization governed by participants in the community.
C4 audits incentivize the discovery of exploits, vulnerabilities, and bugs in smart contracts. Security researchers are rewarded at an increasing rate for finding higher-risk issues. Audit submissions are judged by a knowledgeable security researcher and solidity developer and disclosed to sponsoring developers. C4 does not conduct formal verification regarding the provided code but instead provides final verification.
C4 does not provide any guarantee or warranty regarding the security of this project. All smart contract software should be used at the sole risk and responsibility of users.