Platform: Code4rena
Start Date: 06/06/2023
Pot Size: $60,500 USDC
Total HM: 5
Participants: 50
Period: 8 days
Judge: gzeon
Id: 246
League: ETH
Rank: 20/50
Findings: 3
Award: $128.38
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: libratus
Also found by: 0xcm, BRONZEDISC, Co0nan, Go-Langer, Madalad, MiniGlome, QiuhaoLi, T1MOH, Toshii, Udsen, ernestognw, flacko, joestakey, minhquanym, n1punp, rvierdiiev, sces60107
54.5276 USDC - $54.53
https://github.com/code-423n4/2023-06-llama/blob/main/src/LlamaCore.sol#L334
Llama can't handle target calls with eth value.
In LlamaCore.executeAction(), after we received ethers we don't send them to executor.execute(). So the transaction will revert when executor.execute() tries to send the ethers in actionInfo.value.
Add the test code below to test/LlamaCore.t.sol:ExecuteAction, which sends 1 ethers to an action call:
function test_correctMsgValue() public { bytes memory data = abi.encodeCall(MockProtocol.receiveEth, ()); vm.prank(actionCreatorAaron); uint256 actionId = mpCore.createAction(uint8(Roles.ActionCreator), mpStrategy1, address(mockProtocol), 1 ether, data, ""); ActionInfo memory _actionInfo = ActionInfo( actionId, actionCreatorAaron, uint8(Roles.ActionCreator), mpStrategy1, address(mockProtocol), 1 ether, data ); vm.warp(block.timestamp + 1); _approveAction(approverAdam, _actionInfo); _approveAction(approverAlicia, _actionInfo); vm.warp(block.timestamp + 6 days); mpCore.queueAction(_actionInfo); vm.warp(block.timestamp + 5 days); vm.deal(actionCreatorAaron, 1 ether); vm.prank(actionCreatorAaron); (bool status,) = address(mpCore).call{value: 1 ether}((abi.encodeCall(mpCore.executeAction, (_actionInfo)))); assertTrue(status, "Send 1 ether failed"); }
Then test with forge test -vvv --match-test test_correctMsgValue
, the output has:
│ │ ├─ [11348] LlamaExecutor::execute(MockProtocol: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 1000000000000000000 [1e18], false, 0x4185f8eb) │ │ │ ├─ [0] MockProtocol::receiveEth{value: 1000000000000000000}() │ │ │ │ └─ ← "EvmError: OutOfFund"
As we can see, the transaction reverted because "EvmError: OutOfFund", which means eth is not enough.
Manual review.
Make executor.execute() payable and send msg.value to it in executeAction().
ETH-Transfer
#0 - c4-pre-sort
2023-06-19T11:11:08Z
0xSorryNotSorry marked the issue as duplicate of #255
#1 - c4-pre-sort
2023-06-19T11:12:55Z
0xSorryNotSorry marked the issue as not a duplicate
#2 - c4-pre-sort
2023-06-19T11:14:59Z
0xSorryNotSorry marked the issue as duplicate of #247
#3 - c4-judge
2023-07-02T10:20:26Z
gzeon-c4 changed the severity to 2 (Med Risk)
#4 - c4-judge
2023-07-02T10:30:44Z
gzeon-c4 marked the issue as satisfactory
🌟 Selected for report: Rolezn
Also found by: 0xSmartContract, DavidGiladi, QiuhaoLi, Sathish9098, kutugu, libratus, matrix_0wl, minhquanym
25.6332 USDC - $25.63
https://github.com/code-423n4/2023-06-llama/blob/main/src/LlamaCore.sol#L638
In _deployStrategies(), we directly use config as salt:
uint256 strategyLength = strategyConfigs.length; for (uint256 i = 0; i < strategyLength; i = LlamaUtils.uncheckedIncrement(i)) { bytes32 salt = bytes32(keccak256(strategyConfigs[i])); ILlamaStrategy strategy = ILlamaStrategy(Clones.cloneDeterministic(address(llamaStrategyLogic), salt)); strategy.initialize(strategyConfigs[i]); strategies[strategy] = true; emit StrategyCreated(strategy, llamaStrategyLogic, strategyConfigs[i]); if (i == 0) firstStrategy = strategy; }
So when policyholders want to deploy a strategy with the same config, or there exists a strategy with the same config (e.g., for compatibility of merging two llama instances). The cloneDeterministic will fail (create2 on the same address).
Adding random salt can solve this problem.
https://github.com/code-423n4/2023-06-llama/blob/main/src/LlamaCore.sol#L658
In _deployAccounts(), we directly use config as salt:
uint256 strategyLength = strategyConfigs.length; for (uint256 i = 0; i < strategyLength; i = LlamaUtils.uncheckedIncrement(i)) { bytes32 salt = bytes32(keccak256(strategyConfigs[i])); ILlamaStrategy strategy = ILlamaStrategy(Clones.cloneDeterministic(address(llamaStrategyLogic), salt)); strategy.initialize(strategyConfigs[i]); strategies[strategy] = true; emit StrategyCreated(strategy, llamaStrategyLogic, strategyConfigs[i]); if (i == 0) firstStrategy = strategy; }
So when policyholders want to deploy an account with the same config, or there exists an account with the same config (e.g., for compatibility of merging two llama instances). The cloneDeterministic will fail (create2 on the same address).
Adding random salt can solve this problem.
https://github.com/code-423n4/2023-06-llama/blob/main/src/LlamaCore.sol#L708
/// @dev Returns the EIP-712 domain separator. function _getDomainHash() internal view returns (bytes32) { return keccak256( abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256(bytes(name)), keccak256(bytes("1")), block.chainid, address(this)) ); }
We can use Semantic Versioning 2.0.0 for EIP712_DOMAIN_TYPEHASH's version instead of "1".
https://github.com/code-423n4/2023-06-llama/blob/main/src/interfaces/ILlamaStrategy.sol#L99
vetoed --> voted
https://github.com/code-423n4/2023-06-llama/blob/main/src/lib/Checkpoints.sol#L183
_lowerBinaryLookup is unused.
Since approve is not used in our modified ERC721 policy contract. delete getApproved[id];
is useless.
https://github.com/code-423n4/2023-06-llama/blob/main/src/llama-scripts/LlamaSingleUseScript.sol#L22
Since this is a delegate call, we can use the address(this) directly.
#0 - c4-judge
2023-07-02T16:17:01Z
gzeon-c4 marked the issue as grade-b
🌟 Selected for report: 0xnev
Also found by: 0xSmartContract, K42, QiuhaoLi, VictoryGod, dirk_y, joestakey, ktg, kutugu, libratus, mahdirostami, neko_nyaa, peanuts, xuwinnie
During this audit, I found three high risks and one medium risk, which may result in DoS, assets lost, front-UI fraud, etc.
forge coverage
and genhtml ...
to get an HTML source line-level coverage report. Then I wrote unit tests to improve the test and paid more time to the functions that were not tested before.The codebase is quite strong:
But there are some warnings (search warning
and safety
keywords) to be solved, also they are not so severe to affect security. For example, LlamaCore just checks if the caller currently has permission instead of block.number|timestamp - 1
for simpler and cheaper (no checkpoints), but introduced some race conditions. For another example, aggregate() in LlamaGovernanceScript can make arbitrary calls to be made within the context of LlamaCore
. Wrote these warnings in docs may be better for users.
I think there are two centralization risks:
10 hours
#0 - c4-judge
2023-07-02T15:53:10Z
gzeon-c4 marked the issue as grade-b