Skip to content

Commit 6eb99d4

Browse files
authored
Merge pull request #11 from roycoprotocol/yaudit-fixes
Yaudit fixes
2 parents dce85a9 + a91030c commit 6eb99d4

File tree

4 files changed

+100
-34
lines changed

4 files changed

+100
-34
lines changed

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[profile.default]
2-
solc_version = '0.8.27'
2+
solc_version = '0.8.28'
33
src = "src"
44
out = "out"
55
libs = ["lib"]

src/core/DepositExecutor.sol

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -479,15 +479,15 @@ contract DepositExecutor is ILayerZeroComposer, Ownable2Step, ReentrancyGuardTra
479479

480480
for (uint256 j = 0; j < campaign.inputTokens.length; ++j) {
481481
// Get the amount of this input token deposited by the depositor and the total deposit amount
482-
ERC20 inputToken = campaign.inputTokens[i];
482+
ERC20 inputToken = campaign.inputTokens[j];
483483
uint256 amountDeposited = walletAccounting.depositorToTokenToAmountDeposited[msg.sender][inputToken];
484484
uint256 totalAmountDeposited = walletAccounting.tokenToTotalAmountDeposited[inputToken];
485485

486486
// Update the accounting to reflect the withdrawal
487487
delete walletAccounting.depositorToTokenToAmountDeposited[msg.sender][inputToken];
488488
walletAccounting.tokenToTotalAmountDeposited[inputToken] -= amountDeposited;
489489

490-
if (i == 0) {
490+
if (j == 0) {
491491
// Calculate the receipt tokens owed to the depositor
492492
uint256 receiptTokensOwed = (receiptToken.balanceOf(_weirollWallets[i]) * amountDeposited) / totalAmountDeposited;
493493
// Remit the receipt tokens to the depositor
@@ -511,7 +511,7 @@ contract DepositExecutor is ILayerZeroComposer, Ownable2Step, ReentrancyGuardTra
511511
// If deposit recipe hasn't been executed, return the depositor's share of the input tokens
512512
for (uint256 j = 0; j < campaign.inputTokens.length; ++j) {
513513
// Get the amount of this input token deposited by the depositor
514-
ERC20 inputToken = campaign.inputTokens[i];
514+
ERC20 inputToken = campaign.inputTokens[j];
515515
uint256 amountDeposited = walletAccounting.depositorToTokenToAmountDeposited[msg.sender][inputToken];
516516

517517
// Make sure that the depositor can withdraw all campaign's input tokens atomically to avoid race conditions with recipe execution
@@ -753,12 +753,14 @@ contract DepositExecutor is ILayerZeroComposer, Ownable2Step, ReentrancyGuardTra
753753
}
754754

755755
/**
756-
* @notice Flags the LayerZero V2 OFT as a valid invoker of the lzCompose function.
756+
* @notice Flags the LayerZero V2 OFTs as valid invokers of the lzCompose function.
757757
* @dev Only callable by the contract owner.
758-
* @param _lzV2OFT LayerZero V2 OFT to flag as valid.
758+
* @param _lzV2OFTs LayerZero V2 OFTs to flag as valid.
759759
*/
760-
function setValidLzOFT(address _lzV2OFT) external onlyOwner {
761-
_setValidLzOFT(_lzV2OFT);
760+
function setValidLzOFTs(address[] calldata _lzV2OFTs) external onlyOwner {
761+
for (uint256 i = 0; i < _lzV2OFTs.length; ++i) {
762+
_setValidLzOFT(_lzV2OFTs[i]);
763+
}
762764
}
763765

764766
/**
@@ -771,14 +773,29 @@ contract DepositExecutor is ILayerZeroComposer, Ownable2Step, ReentrancyGuardTra
771773
emit ValidLzOftRemoved(_lzV2OFT);
772774
}
773775

776+
/**
777+
* @notice Sets owners for the specified campaigns.
778+
* @dev Only callable by the contract owner.
779+
* @param _sourceMarketHashes The market hashes on the source chain used to identify the corresponding campaigns on the destination.
780+
* @param _campaignOwners The addresses of the campaign owners.
781+
*/
782+
function setCampaignOwners(bytes32[] calldata _sourceMarketHashes, address[] calldata _campaignOwners) external onlyOwner {
783+
// Make sure the each campaign identified by its source market hash has a corresponding owner
784+
require(_sourceMarketHashes.length == _campaignOwners.length, ArrayLengthMismatch());
785+
786+
for (uint256 i = 0; i < _sourceMarketHashes.length; ++i) {
787+
_setCampaignOwner(_sourceMarketHashes[i], _campaignOwners[i]);
788+
}
789+
}
790+
774791
/**
775792
* @notice Sets a new owner for the specified campaign.
776793
* @dev Only callable by the contract owner or the current owner of the campaign.
777794
* @param _sourceMarketHash The market hash on the source chain used to identify the corresponding campaign on the destination.
778-
* @param _owner The address of the campaign owner.
795+
* @param _campaignOwner The address of the campaign owner.
779796
*/
780-
function setNewCampaignOwner(bytes32 _sourceMarketHash, address _owner) external onlyCampaignOwnerOrDepositExecutorOwner(_sourceMarketHash) {
781-
_setCampaignOwner(_sourceMarketHash, _owner);
797+
function setNewCampaignOwner(bytes32 _sourceMarketHash, address _campaignOwner) external onlyCampaignOwnerOrDepositExecutorOwner(_sourceMarketHash) {
798+
_setCampaignOwner(_sourceMarketHash, _campaignOwner);
782799
}
783800

784801
/**
@@ -803,7 +820,7 @@ contract DepositExecutor is ILayerZeroComposer, Ownable2Step, ReentrancyGuardTra
803820

804821
/**
805822
* @notice Sets the campaign verification status to false.
806-
* @notice Deposit Recipe cannot be executed and withdrawals are blocked until verified.
823+
* @notice Deposit Recipe cannot be executed until verified.
807824
* @dev Only callable by the campaign verifier.
808825
* @param _sourceMarketHash The market hash on the source chain used to identify the corresponding campaign on the destination.
809826
*/

src/core/DepositLocker.sol

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
3030
uint256 public constant MAX_DEPOSITORS_PER_BRIDGE = 300;
3131

3232
/// @notice The duration of time that depositors have after the market's green light is given to rage quit before they can be bridged.
33-
uint256 public constant RAGE_QUIT_PERIOD_DURATION = 48 hours;
33+
uint256 public constant RAGE_QUIT_PERIOD_DURATION = 0 hours;
3434

3535
/// @notice The code hash of the Uniswap V2 Pair contract.
3636
bytes32 internal constant UNISWAP_V2_PAIR_CODE_HASH = 0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5;
@@ -99,6 +99,12 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
9999
/// @notice The Uniswap V2 router on the source chain.
100100
IUniswapV2Router01 public immutable UNISWAP_V2_ROUTER;
101101

102+
/// @notice The hash of the Weiroll Wallet code
103+
bytes32 public immutable WEIROLL_WALLET_PROXY_CODE_HASH;
104+
105+
/// @notice The address of the Weiroll Wallet that deposited into the CCDM in this transaction.
106+
address public transient currentlyDepositingWeirollWallet;
107+
102108
/// @notice The party that green lights bridging on a per market basis
103109
address public greenLighter;
104110

@@ -240,6 +246,9 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
240246
*/
241247
event GreenLightTurnedOff(bytes32 indexed marketHash);
242248

249+
/// @notice Error emitted when calling deposit from an address that isn't a Weiroll Wallet.
250+
error OnlyWeirollWallet();
251+
243252
/// @notice Error emitted when trying to deposit into the locker for a Royco market that is either not created or has an undeployed input token.
244253
error RoycoMarketNotInitialized();
245254

@@ -279,10 +288,30 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
279288
/// @notice Error emitted when bridging all the specified deposits fails.
280289
error FailedToBridgeAllDeposits();
281290

291+
/// @notice Error emitted when the lengths of the source market hashes and owners array don't match in the constructor.
292+
error ArrayLengthMismatch();
293+
294+
/// @notice Error emitted when transferring back excess msg.value fails.
295+
error RefundFailed();
296+
282297
/*//////////////////////////////////////////////////////////////
283298
Modifiers
284299
//////////////////////////////////////////////////////////////*/
285300

301+
/// @dev Modifier to ensure the caller is a Weiroll Wallet created using the clone with immutable args pattern.
302+
modifier onlyWeirollWallet() {
303+
bytes memory code = msg.sender.code;
304+
bytes32 codeHash;
305+
assembly ("memory-safe") {
306+
// Get code hash of the runtime bytecode without the immutable args
307+
codeHash := keccak256(add(code, 32), 56)
308+
}
309+
310+
// Check that the length is valid and the codeHash matches that of a Weiroll Wallet proxy
311+
require(code.length == 195 && codeHash == WEIROLL_WALLET_PROXY_CODE_HASH, OnlyWeirollWallet());
312+
_;
313+
}
314+
286315
/// @dev Modifier to ensure the caller is the authorized multisig for the market.
287316
modifier onlyGreenLighter() {
288317
require(msg.sender == greenLighter, OnlyGreenLighter());
@@ -334,6 +363,11 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
334363
RECIPE_MARKET_HUB = _recipeMarketHub;
335364
WRAPPED_NATIVE_ASSET_TOKEN = IWETH(_uniswap_v2_router.WETH());
336365
UNISWAP_V2_ROUTER = _uniswap_v2_router;
366+
WEIROLL_WALLET_PROXY_CODE_HASH = keccak256(
367+
abi.encodePacked(
368+
hex"363d3d3761008b603836393d3d3d3661008b013d73", _recipeMarketHub.WEIROLL_WALLET_IMPLEMENTATION(), hex"5af43d82803e903d91603657fd5bf3"
369+
)
370+
);
337371
ccdmNonce = 1; // The first CCDM bridge transaction will have a nonce of 1
338372

339373
for (uint256 i = 0; i < _lzV2OFTs.length; ++i) {
@@ -357,7 +391,7 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
357391
/**
358392
* @notice Called by the deposit script from the depositor's Weiroll wallet.
359393
*/
360-
function deposit() external nonReentrant {
394+
function deposit() external nonReentrant onlyWeirollWallet {
361395
// Get Weiroll Wallet's market hash, depositor/owner/AP, and amount deposited
362396
WeirollWallet wallet = WeirollWallet(payable(msg.sender));
363397
bytes32 targetMarketHash = wallet.marketHash();
@@ -386,6 +420,9 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
386420
walletInfo.ccdmNonceOnDeposit = ccdmNonce;
387421
walletInfo.amountDeposited = amountDeposited;
388422

423+
// Set the depositing Weiroll Wallet in transient state
424+
currentlyDepositingWeirollWallet = msg.sender;
425+
389426
// Emit deposit event
390427
emit UserDeposited(targetMarketHash, depositor, amountDeposited);
391428
}
@@ -428,8 +465,6 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
428465
* @param _depositors The addresses of the depositors (APs) to bridge
429466
*/
430467
function bridgeSingleTokens(bytes32 _marketHash, address[] calldata _depositors) external payable readyToBridge(_marketHash) nonReentrant {
431-
require(_depositors.length <= MAX_DEPOSITORS_PER_BRIDGE, DepositorsPerBridgeLimitExceeded());
432-
433468
// The CCDM nonce for this CCDM bridge transaction
434469
uint256 nonce = ccdmNonce;
435470
// Initialize compose message
@@ -455,6 +490,8 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
455490

456491
// Ensure that at least one depositor was included in the bridge payload
457492
require(totalAmountToBridge > 0, MustBridgeAtLeastOneDepositor());
493+
// Ensure that the number of depositors bridged is less than the globally defined limit
494+
require(numDepositorsIncluded <= MAX_DEPOSITORS_PER_BRIDGE, DepositorsPerBridgeLimitExceeded());
458495

459496
// Resize the compose message to reflect the actual number of depositors included in the payload
460497
composeMsg.resizeComposeMsg(numDepositorsIncluded);
@@ -476,7 +513,8 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
476513

477514
// Refund any excess value sent with the transaction
478515
if (msg.value > bridgingFee) {
479-
payable(msg.sender).transfer(msg.value - bridgingFee);
516+
(bool success,) = payable(msg.sender).call{ value: msg.value - bridgingFee }("");
517+
require(success, RefundFailed());
480518
}
481519

482520
// Emit event to keep track of bridged deposits
@@ -504,8 +542,6 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
504542
readyToBridge(_marketHash)
505543
nonReentrant
506544
{
507-
require(_depositors.length <= MAX_DEPOSITORS_PER_BRIDGE, DepositorsPerBridgeLimitExceeded());
508-
509545
// The CCDM nonce for this CCDM bridge transaction
510546
uint256 nonce = ccdmNonce;
511547

@@ -571,8 +607,10 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
571607
// Ensure that at least one depositor was included in the bridge payload
572608
require(totals.token0_TotalAmountToBridge > 0 && totals.token1_TotalAmountToBridge > 0, MustBridgeAtLeastOneDepositor());
573609

574-
// Resize the compose messages to reflect the actual number of depositors bridged
575610
uint256 numDepositorsIncluded = params.numDepositorsIncluded;
611+
// Ensure that the number of depositors bridged is less than the globally defined limit
612+
require(numDepositorsIncluded <= MAX_DEPOSITORS_PER_BRIDGE, DepositorsPerBridgeLimitExceeded());
613+
// Resize the compose messages to reflect the actual number of depositors bridged
576614
token0_ComposeMsg.resizeComposeMsg(numDepositorsIncluded);
577615
token1_ComposeMsg.resizeComposeMsg(numDepositorsIncluded);
578616

@@ -760,7 +798,8 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
760798

761799
// Refund excess value sent with the transaction
762800
if (msg.value > totalBridgingFee) {
763-
payable(msg.sender).transfer(msg.value - totalBridgingFee);
801+
(bool success,) = payable(msg.sender).call{ value: msg.value - totalBridgingFee }("");
802+
require(success, RefundFailed());
764803
}
765804

766805
// Emit event to keep track of bridged deposits
@@ -899,24 +938,29 @@ contract DepositLocker is Ownable2Step, ReentrancyGuardTransient {
899938
}
900939

901940
/**
902-
* @notice Sets the owner of an LP token market.
941+
* @notice Sets the owners of LP token markets.
903942
* @dev Only callable by the contract owner.
904-
* @param _marketHash The market hash to set the LP token owner for.
905-
* @param _lpMarketOwner Address of the LP token market owner.
906-
*/
907-
function setLpMarketOwner(bytes32 _marketHash, address _lpMarketOwner) external onlyOwner {
908-
marketHashToLpMarketOwner[_marketHash] = _lpMarketOwner;
909-
emit LpMarketOwnerSet(_marketHash, _lpMarketOwner);
943+
* @param _marketHashes The market hashes to set the LP market owners for.
944+
* @param _lpMarketOwners Addresses of the LP market owners.
945+
*/
946+
function setLpMarketOwners(bytes32[] calldata _marketHashes, address[] calldata _lpMarketOwners) external onlyOwner {
947+
require(_marketHashes.length == _lpMarketOwners.length, ArrayLengthMismatch());
948+
for (uint256 i = 0; i < _marketHashes.length; ++i) {
949+
marketHashToLpMarketOwner[_marketHashes[i]] = _lpMarketOwners[i];
950+
emit LpMarketOwnerSet(_marketHashes[i], _lpMarketOwners[i]);
951+
}
910952
}
911953

912954
/**
913-
* @notice Sets the LayerZero V2 OFT for the underlying token.
914-
* @notice _lzV2OFT must implement IOFT.
955+
* @notice Sets the LayerZero V2 OFTs for the underlying tokens.
956+
* @notice Elements of _lzV2OFTs must implement IOFT.
915957
* @dev Only callable by the contract owner.
916-
* @param _lzV2OFT LayerZero OFT to use to bridge the specified token.
958+
* @param _lzV2OFTs LayerZero OFTs to use to bridge the underlying tokens.
917959
*/
918-
function setLzOFT(IOFT _lzV2OFT) external onlyOwner {
919-
_setLzV2OFTForToken(_lzV2OFT);
960+
function setLzOFTs(IOFT[] calldata _lzV2OFTs) external onlyOwner {
961+
for (uint256 i = 0; i < _lzV2OFTs.length; ++i) {
962+
_setLzV2OFTForToken(_lzV2OFTs[i]);
963+
}
920964
}
921965

922966
/**

test/TestBridgeDeposits.t.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,13 @@ contract Test_BridgeDeposits_DepositLocker is RecipeMarketHubTestBase {
362362

363363
vm.stopPrank();
364364

365+
bytes32[] memory marketHashes = new bytes32[](1);
366+
marketHashes[0] = marketHash;
367+
address[] memory owners = new address[](1);
368+
owners[0] = IP_ADDRESS;
369+
365370
vm.startPrank(OWNER_ADDRESS);
366-
depositLocker.setLpMarketOwner(marketHash, IP_ADDRESS);
371+
depositLocker.setLpMarketOwners(marketHashes, owners);
367372
vm.stopPrank();
368373

369374
vm.startPrank(GREEN_LIGHTER_ADDRESS);

0 commit comments

Comments
 (0)