diff --git a/src/builder/Accounts.sol b/src/builder/Accounts.sol index a73197f6..a98c0b12 100644 --- a/src/builder/Accounts.sol +++ b/src/builder/Accounts.sol @@ -8,21 +8,20 @@ import {PaymentInfo} from "./PaymentInfo.sol"; import {TokenWrapper} from "./TokenWrapper.sol"; library Accounts { + error QuarkSecretNotFound(address account); + struct ChainAccounts { uint256 chainId; - QuarkState[] quarkStates; + QuarkSecret[] quarkSecrets; AssetPositions[] assetPositionsList; CometPositions[] cometPositions; MorphoPositions[] morphoPositions; MorphoVaultPositions[] morphoVaultPositions; } - // We map this to the Portfolio data structure that the client will already have. - // This includes fields that builder may not necessarily need, however it makes - // the client encoding that much simpler. - struct QuarkState { + struct QuarkSecret { address account; - uint96 quarkNextNonce; + bytes32 nonceSecret; } // Similarly, this is designed to intentionally reduce the encoding burden for the client @@ -182,16 +181,17 @@ library Accounts { return findAssetPositions(assetAddress, chainAccounts.assetPositionsList); } - function findQuarkState(address account, Accounts.QuarkState[] memory quarkStates) + function findQuarkSecret(address account, Accounts.QuarkSecret[] memory quarkSecrets) internal pure - returns (Accounts.QuarkState memory state) + returns (Accounts.QuarkSecret memory) { - for (uint256 i = 0; i < quarkStates.length; ++i) { - if (quarkStates[i].account == account) { - return state = quarkStates[i]; + for (uint256 i = 0; i < quarkSecrets.length; ++i) { + if (quarkSecrets[i].account == account) { + return quarkSecrets[i]; } } + revert QuarkSecretNotFound(account); } function findChainAccountsWithPaymentInfo( diff --git a/src/builder/Actions.sol b/src/builder/Actions.sol index 586badd1..4adc40ae 100644 --- a/src/builder/Actions.sol +++ b/src/builder/Actions.sol @@ -58,6 +58,9 @@ library Actions { uint256 constant SWAP_EXPIRY_BUFFER = 3 days; uint256 constant TRANSFER_EXPIRY_BUFFER = 7 days; + /* total plays */ + uint256 constant RECURRING_SWAP_TOTAL_PLAYS = 500; + uint256 constant AVERAGE_BLOCK_TIME = 12 seconds; uint256 constant RECURRING_SWAP_MAX_SLIPPAGE = 1e17; // 1% @@ -229,6 +232,14 @@ library Actions { bool useQuotecall; } + // Note: To avoid stack too deep errors + struct RecurringSwapLocalVars { + Accounts.ChainAccounts accounts; + Accounts.AssetPositions sellTokenAssetPositions; + Accounts.AssetPositions buyTokenAssetPositions; + Accounts.QuarkSecret accountSecret; + } + /* ===== Output Types ===== */ // With Action, we try to define fields that are as 1:1 as possible with @@ -245,6 +256,11 @@ library Actions { address paymentToken; string paymentTokenSymbol; uint256 paymentMaxCost; + // The secret used to generate the hash chain for a replayable operation. For non-replayable + // operations, the `nonce` will be the `nonceSecret` (the hash chain has a length of 1) + bytes32 nonceSecret; + // The number of times an operation can be played. For non-replayable operations, this will be 1 + uint256 totalPlays; } struct BorrowActionContext { @@ -586,14 +602,15 @@ library Actions { Accounts.AssetPositions memory srcUSDCPositions = Accounts.findAssetPositions("USDC", srcChainAccounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(bridge.sender, srcChainAccounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(bridge.sender, srcChainAccounts.quarkSecrets); bytes[] memory scriptSources = new bytes[](1); scriptSources[0] = CCTP.bridgeScriptSource(); // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(scriptSources[0]), scriptCalldata: CCTP.encodeBridgeUSDC( @@ -625,7 +642,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, bridge.srcChainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, bridge.srcChainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, bridge.srcChainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -642,7 +661,8 @@ library Actions { Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(borrowInput.chainId, borrowInput.chainAccountsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(borrowInput.borrower, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(borrowInput.borrower, accounts.quarkSecrets); Accounts.AssetPositions memory borrowAssetPositions = Accounts.findAssetPositions(borrowInput.assetSymbol, accounts.assetPositionsList); @@ -669,7 +689,7 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(CometSupplyMultipleAssetsAndBorrow).creationCode), scriptCalldata: scriptCalldata, @@ -700,7 +720,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -717,7 +739,7 @@ library Actions { Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(repayInput.chainId, repayInput.chainAccountsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(repayInput.repayer, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(repayInput.repayer, accounts.quarkSecrets); Accounts.AssetPositions memory repayAssetPositions = Accounts.findAssetPositions(repayInput.assetSymbol, accounts.assetPositionsList); @@ -744,7 +766,7 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(CometRepayAndWithdrawMultipleAssets).creationCode), scriptCalldata: scriptCalldata, @@ -775,7 +797,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -795,7 +819,7 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(cometSupply.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(cometSupply.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(cometSupply.sender, accounts.quarkSecrets); bytes memory scriptCalldata; if (Strings.stringEqIgnoreCase(cometSupply.assetSymbol, "ETH")) { @@ -807,7 +831,7 @@ library Actions { } // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(CometSupplyActions).creationCode), scriptCalldata: scriptCalldata, @@ -834,7 +858,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, cometSupply.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometSupply.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometSupply.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -854,8 +880,8 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(cometWithdraw.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = - Accounts.findQuarkState(cometWithdraw.withdrawer, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(cometWithdraw.withdrawer, accounts.quarkSecrets); bytes memory scriptCalldata; if (Strings.stringEqIgnoreCase(cometWithdraw.assetSymbol, "ETH")) { @@ -867,7 +893,7 @@ library Actions { } // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(CometWithdrawActions).creationCode), scriptCalldata: scriptCalldata, @@ -894,7 +920,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, cometWithdraw.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometWithdraw.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, cometWithdraw.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -914,7 +942,7 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(transfer.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(transfer.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(transfer.sender, accounts.quarkSecrets); bytes memory scriptCalldata; if (Strings.stringEqIgnoreCase(transfer.assetSymbol, "ETH")) { @@ -930,7 +958,7 @@ library Actions { } // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(TransferActions).creationCode), scriptCalldata: scriptCalldata, @@ -958,7 +986,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, transfer.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, transfer.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, transfer.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -975,7 +1005,8 @@ library Actions { Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(borrowInput.chainId, borrowInput.chainAccountsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(borrowInput.borrower, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(borrowInput.borrower, accounts.quarkSecrets); Accounts.AssetPositions memory borrowAssetPositions = Accounts.findAssetPositions(borrowInput.assetSymbol, accounts.assetPositionsList); @@ -993,7 +1024,7 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode), scriptCalldata: scriptCalldata, @@ -1026,7 +1057,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, borrowInput.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, borrowInput.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1043,7 +1076,7 @@ library Actions { Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(repayInput.chainId, repayInput.chainAccountsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(repayInput.repayer, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(repayInput.repayer, accounts.quarkSecrets); Accounts.AssetPositions memory repayAssetPositions = Accounts.findAssetPositions(repayInput.assetSymbol, accounts.assetPositionsList); @@ -1061,7 +1094,7 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode), scriptCalldata: scriptCalldata, @@ -1095,7 +1128,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, repayInput.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, repayInput.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1115,7 +1150,7 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(vaultSupply.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(vaultSupply.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(vaultSupply.sender, accounts.quarkSecrets); bytes memory scriptCalldata = abi.encodeWithSelector( MorphoVaultActions.deposit.selector, @@ -1126,7 +1161,7 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoVaultActions).creationCode), scriptCalldata: scriptCalldata, @@ -1153,7 +1188,9 @@ library Actions { // Null address for OFFCHAIN payment. paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, vaultSupply.chainId).token : address(0), paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, vaultSupply.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, vaultSupply.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1173,8 +1210,8 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(vaultWithdraw.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = - Accounts.findQuarkState(vaultWithdraw.withdrawer, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = + Accounts.findQuarkSecret(vaultWithdraw.withdrawer, accounts.quarkSecrets); bytes memory scriptCalldata = abi.encodeWithSelector( MorphoVaultActions.withdraw.selector, @@ -1184,7 +1221,7 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(MorphoVaultActions).creationCode), scriptCalldata: scriptCalldata, @@ -1213,7 +1250,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, vaultWithdraw.chainId).token : address(0), paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, vaultWithdraw.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, vaultWithdraw.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1233,10 +1272,10 @@ library Actions { Accounts.AssetPositions memory assetPositions = Accounts.findAssetPositions(wrapOrUnwrap.assetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(wrapOrUnwrap.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(wrapOrUnwrap.sender, accounts.quarkSecrets); // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode), scriptCalldata: TokenWrapper.encodeActionToWrapOrUnwrap( @@ -1267,7 +1306,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, wrapOrUnwrap.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, wrapOrUnwrap.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, wrapOrUnwrap.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1292,7 +1333,7 @@ library Actions { Accounts.AssetPositions memory feeTokenAssetPositions = Accounts.findAssetPositions(swap.feeAssetSymbol, accounts.assetPositionsList); - Accounts.QuarkState memory accountState = Accounts.findQuarkState(swap.sender, accounts.quarkStates); + Accounts.QuarkSecret memory accountSecret = Accounts.findQuarkSecret(swap.sender, accounts.quarkSecrets); // TODO: Handle wrapping ETH? Do we need to? bytes memory scriptCalldata = abi.encodeWithSelector( @@ -1307,7 +1348,7 @@ library Actions { // Construct QuarkOperation IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), + nonce: accountSecret.nonceSecret, isReplayable: false, scriptAddress: CodeJarHelper.getCodeAddress(type(ApproveAndSwap).creationCode), scriptCalldata: scriptCalldata, @@ -1343,7 +1384,9 @@ library Actions { ? PaymentInfo.knownToken(payment.currency, swap.chainId).token : PaymentInfo.NON_TOKEN_PAYMENT, paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0, + nonceSecret: accountSecret.nonceSecret, + totalPlays: 1 }); return (quarkOperation, action); @@ -1354,56 +1397,68 @@ library Actions { pure returns (IQuarkWallet.QuarkOperation memory, Action memory) { - bytes[] memory scriptSources = new bytes[](1); - scriptSources[0] = type(RecurringSwap).creationCode; - - Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(swap.chainId, swap.chainAccountsList); - - Accounts.AssetPositions memory sellTokenAssetPositions = - Accounts.findAssetPositions(swap.sellAssetSymbol, accounts.assetPositionsList); - - Accounts.AssetPositions memory buyTokenAssetPositions = - Accounts.findAssetPositions(swap.buyAssetSymbol, accounts.assetPositionsList); - - Accounts.QuarkState memory accountState = Accounts.findQuarkState(swap.sender, accounts.quarkStates); + RecurringSwapLocalVars memory localVars; + // Local scope to avoid stack too deep + { + Accounts.ChainAccounts memory accounts = Accounts.findChainAccounts(swap.chainId, swap.chainAccountsList); + localVars = RecurringSwapLocalVars({ + accounts: accounts, + accountSecret: Accounts.findQuarkSecret(swap.sender, accounts.quarkSecrets), + sellTokenAssetPositions: Accounts.findAssetPositions(swap.sellAssetSymbol, accounts.assetPositionsList), + buyTokenAssetPositions: Accounts.findAssetPositions(swap.buyAssetSymbol, accounts.assetPositionsList) + }); + } - RecurringSwap.SwapParams memory swapParams = RecurringSwap.SwapParams({ - uniswapRouter: UniswapRouter.knownRouter(swap.chainId), - recipient: swap.sender, - tokenIn: swap.sellToken, - tokenOut: swap.buyToken, - amount: swap.isExactOut ? swap.buyAmount : swap.sellAmount, - isExactOut: swap.isExactOut, - path: swap.path - }); - (address[] memory priceFeeds, bool[] memory shouldInvert) = PriceFeeds.findPriceFeedPath({ - inputAssetSymbol: PriceFeeds.convertToPriceFeedSymbol(swap.sellAssetSymbol), - outputAssetSymbol: PriceFeeds.convertToPriceFeedSymbol(swap.buyAssetSymbol), - chainId: swap.chainId - }); - RecurringSwap.SlippageParams memory slippageParams = RecurringSwap.SlippageParams({ - maxSlippage: RECURRING_SWAP_MAX_SLIPPAGE, - priceFeeds: priceFeeds, - shouldInvert: shouldInvert - }); - RecurringSwap.SwapConfig memory swapConfig = RecurringSwap.SwapConfig({ - startTime: swap.blockTimestamp - AVERAGE_BLOCK_TIME, - interval: swap.interval, - swapParams: swapParams, - slippageParams: slippageParams - }); - // TODO: Handle wrapping ETH? Do we need to? - bytes memory scriptCalldata = abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig); + RecurringSwap.SwapConfig memory swapConfig; + // Local scope to avoid stack too deep + { + RecurringSwap.SwapParams memory swapParams = RecurringSwap.SwapParams({ + uniswapRouter: UniswapRouter.knownRouter(swap.chainId), + recipient: swap.sender, + tokenIn: swap.sellToken, + tokenOut: swap.buyToken, + amount: swap.isExactOut ? swap.buyAmount : swap.sellAmount, + isExactOut: swap.isExactOut, + path: swap.path + }); + (address[] memory priceFeeds, bool[] memory shouldInvert) = PriceFeeds.findPriceFeedPath({ + inputAssetSymbol: PriceFeeds.convertToPriceFeedSymbol(swap.sellAssetSymbol), + outputAssetSymbol: PriceFeeds.convertToPriceFeedSymbol(swap.buyAssetSymbol), + chainId: swap.chainId + }); + RecurringSwap.SlippageParams memory slippageParams = RecurringSwap.SlippageParams({ + maxSlippage: RECURRING_SWAP_MAX_SLIPPAGE, + priceFeeds: priceFeeds, + shouldInvert: shouldInvert + }); + swapConfig = RecurringSwap.SwapConfig({ + startTime: swap.blockTimestamp - AVERAGE_BLOCK_TIME, + interval: swap.interval, + swapParams: swapParams, + slippageParams: slippageParams + }); + } // Construct QuarkOperation - IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({ - nonce: bytes32(uint256(accountState.quarkNextNonce)), - isReplayable: true, - scriptAddress: CodeJarHelper.getCodeAddress(type(RecurringSwap).creationCode), - scriptCalldata: scriptCalldata, - scriptSources: scriptSources, - expiry: type(uint256).max - }); + IQuarkWallet.QuarkOperation memory quarkOperation; + // Local scope to avoid stack too deep + { + bytes[] memory scriptSources = new bytes[](1); + scriptSources[0] = type(RecurringSwap).creationCode; + + // TODO: Handle wrapping ETH? Do we need to? + bytes memory scriptCalldata = abi.encodeWithSelector(RecurringSwap.swap.selector, swapConfig); + + bytes32 nonce = generateNonceFromSecret(localVars.accountSecret.nonceSecret, RECURRING_SWAP_TOTAL_PLAYS); + quarkOperation = IQuarkWallet.QuarkOperation({ + nonce: nonce, + isReplayable: true, + scriptAddress: CodeJarHelper.getCodeAddress(type(RecurringSwap).creationCode), + scriptCalldata: scriptCalldata, + scriptSources: scriptSources, + expiry: type(uint256).max + }); + } // Construct Action RecurringSwapActionContext memory recurringSwapActionContext = RecurringSwapActionContext({ @@ -1411,11 +1466,11 @@ library Actions { inputAmount: swap.sellAmount, inputAssetSymbol: swap.sellAssetSymbol, inputToken: swap.sellToken, - inputTokenPrice: sellTokenAssetPositions.usdPrice, + inputTokenPrice: localVars.sellTokenAssetPositions.usdPrice, outputAmount: swap.buyAmount, outputAssetSymbol: swap.buyAssetSymbol, outputToken: swap.buyToken, - outputTokenPrice: buyTokenAssetPositions.usdPrice, + outputTokenPrice: localVars.buyTokenAssetPositions.usdPrice, isExactOut: swap.isExactOut, interval: swap.interval }); @@ -1429,7 +1484,9 @@ library Actions { // Null address for OFFCHAIN payment. paymentToken: payment.isToken ? PaymentInfo.knownToken(payment.currency, swap.chainId).token : address(0), paymentTokenSymbol: payment.currency, - paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0 + paymentMaxCost: payment.isToken ? PaymentInfo.findMaxCost(payment, swap.chainId) : 0, + nonceSecret: localVars.accountSecret.nonceSecret, + totalPlays: RECURRING_SWAP_TOTAL_PLAYS }); return (quarkOperation, action); @@ -1487,6 +1544,19 @@ library Actions { return result; } + function generateNonceFromSecret(bytes32 secret, uint256 totalPlays) internal pure returns (bytes32) { + uint256 replayCount = totalPlays - 1; + assembly ("memory-safe") { + let ptr := mload(0x40) // Get free memory pointer + mstore(ptr, secret) // Store initial secret at ptr + + for { let i := 0 } lt(i, replayCount) { i := add(i, 1) } { mstore(ptr, keccak256(ptr, 32)) } + + secret := mload(ptr) // Load final result + } + return secret; + } + // These structs are mostly used internally and returned in serialized format as bytes: actionContext // The caller can then decode them back into their struct form. // These empty husk functions exist so that the structs make it into the abi so the clients can know how to decode them. diff --git a/src/builder/QuarkBuilder.sol b/src/builder/QuarkBuilder.sol index a9872151..847d6e3e 100644 --- a/src/builder/QuarkBuilder.sol +++ b/src/builder/QuarkBuilder.sol @@ -20,7 +20,7 @@ import {List} from "./List.sol"; contract QuarkBuilder { /* ===== Constants ===== */ - string constant VERSION = "0.1.0"; + string constant VERSION = "0.1.1"; /* ===== Custom Errors ===== */ diff --git a/test/builder/QuarkBuilderCometBorrow.t.sol b/test/builder/QuarkBuilderCometBorrow.t.sol index 19a3fbb2..f6d36eda 100644 --- a/test/builder/QuarkBuilderCometBorrow.t.sol +++ b/test/builder/QuarkBuilderCometBorrow.t.sol @@ -84,7 +84,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK cometPortfolios: emptyCometPortfolios_(), @@ -94,7 +94,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -154,6 +154,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -163,6 +165,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -209,7 +213,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 10e18, 0, 0), // user has 10 ETH cometPortfolios: emptyCometPortfolios_(), @@ -219,7 +223,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -275,6 +279,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -284,6 +290,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -320,7 +328,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 10e18, 0), // user has 1 USDC, 10 LINK cometPortfolios: emptyCometPortfolios_(), @@ -330,7 +338,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -397,6 +405,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -406,6 +416,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -450,7 +462,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 10e18, 0), // user has 10 LINK and 0 USDC cometPortfolios: emptyCometPortfolios_(), @@ -460,7 +472,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -522,6 +534,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -531,6 +545,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment max is set to .5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -577,7 +593,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(3e6, 0, 0, 0), // 3 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -586,8 +602,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, - account: address(0xb0b), - nextNonce: 2, + account: address(0xa11ce), + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 5e18, 0), cometPortfolios: emptyCometPortfolios_(), @@ -649,6 +665,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -686,6 +704,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, chainPortfolios[1].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -696,6 +716,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -719,6 +741,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 1e6, "payment should have max cost of 1e6"); + assertEq(result.actions[1].nonceSecret, chainPortfolios[1].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -765,7 +789,7 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -774,8 +798,9 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, - account: address(0xb0b), - nextNonce: 2, + // TODO: if want to test different accounts, can set bridge as b0b and send as allice + account: address(0xa11ce), + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), @@ -837,6 +862,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -874,6 +901,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, chainPortfolios[1].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -884,6 +913,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -907,6 +938,8 @@ contract QuarkBuilderCometBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.2e6, "payment should have max cost of 0.2e6"); + assertEq(result.actions[1].nonceSecret, chainPortfolios[1].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = USDC_PRICE; diff --git a/test/builder/QuarkBuilderCometRepay.t.sol b/test/builder/QuarkBuilderCometRepay.t.sol index c297d6ae..111f25aa 100644 --- a/test/builder/QuarkBuilderCometRepay.t.sol +++ b/test/builder/QuarkBuilderCometRepay.t.sol @@ -83,7 +83,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 0, 1e18), // user does not have enough USDC cometPortfolios: emptyCometPortfolios_(), @@ -111,7 +111,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC cometPortfolios: emptyCometPortfolios_(), @@ -121,7 +121,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -182,6 +182,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -191,6 +193,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -237,7 +241,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 1e18, 0, 0), // has 1 ETH cometPortfolios: emptyCometPortfolios_(), @@ -247,7 +251,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -305,6 +309,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -314,6 +320,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -350,7 +358,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(2e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -360,7 +368,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -428,6 +436,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -437,6 +447,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -472,17 +484,11 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 0.5e6}); // action costs .5 USDC - uint256[] memory collateralAmounts = new uint256[](1); - collateralAmounts[0] = 1e6; - - string[] memory collateralAssetSymbols = new string[](1); - collateralAssetSymbols[0] = "USDC"; - ChainPortfolio[] memory chainPortfolios = new ChainPortfolio[](2); chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 1e18), cometPortfolios: emptyCometPortfolios_(), @@ -492,7 +498,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -500,15 +506,26 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { morphoVaultPortfolios: emptyMorphoVaultPortfolios_() }); - QuarkBuilder.BuilderResult memory result = builder.cometRepay( - repayIntent_( + QuarkBuilder.CometRepayIntent memory repayIntent; + // Local scope to avoid stack too deep + { + uint256[] memory collateralAmounts = new uint256[](1); + collateralAmounts[0] = 1e6; + + string[] memory collateralAssetSymbols = new string[](1); + collateralAssetSymbols[0] = "USDC"; + + repayIntent = repayIntent_( 1, cometWeth_(1), "WETH", 1e18, // repaying 1 WETH collateralAssetSymbols, collateralAmounts // and withdrawing 1 USDC - ), + ); + } + QuarkBuilder.BuilderResult memory result = builder.cometRepay( + repayIntent, chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) // user is paying with USDC that is currently supplied as collateral ); @@ -538,7 +555,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { CometRepayAndWithdrawMultipleAssets.run.selector, cometWeth_(1), collateralTokens, - collateralAmounts, + repayIntent.collateralAmounts, weth_(1), 1e18 ), @@ -556,6 +573,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -565,6 +584,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment max is set to .5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = USDC_PRICE; @@ -576,8 +597,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { amount: 1e18, assetSymbol: "WETH", chainId: 1, - collateralAmounts: collateralAmounts, - collateralAssetSymbols: collateralAssetSymbols, + collateralAmounts: repayIntent.collateralAmounts, + collateralAssetSymbols: repayIntent.collateralAssetSymbols, collateralTokenPrices: collateralTokenPrices, collateralTokens: collateralTokens, comet: cometWeth_(1), @@ -611,7 +632,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -620,8 +641,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, - account: address(0xb0b), - nextNonce: 2, + account: address(0xa11ce), + nonceSecret: bytes32(uint256(2)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), @@ -684,6 +705,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -721,6 +744,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, chainPortfolios[1].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -731,6 +756,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -754,6 +781,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.2e6, "payment should have max cost of 0.2e6"); + assertEq(result.actions[1].nonceSecret, chainPortfolios[1].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](1); collateralTokenPrices[0] = LINK_PRICE; @@ -804,7 +833,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC cometPortfolios: cometPortfolios, @@ -869,6 +898,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -878,6 +909,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](0); @@ -928,7 +961,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -938,7 +971,7 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base cometPortfolios: cometPortfolios, @@ -1006,6 +1039,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, chainPortfolios[0].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -1039,6 +1074,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, chainPortfolios[1].nonceSecret, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -1048,6 +1085,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, chainPortfolios[0].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1072,6 +1111,8 @@ contract QuarkBuilderCometRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[1].nonceSecret, chainPortfolios[1].nonceSecret, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); uint256[] memory collateralTokenPrices = new uint256[](0); diff --git a/test/builder/QuarkBuilderCometSupply.t.sol b/test/builder/QuarkBuilderCometSupply.t.sol index c39305f0..c04c476e 100644 --- a/test/builder/QuarkBuilderCometSupply.t.sol +++ b/test/builder/QuarkBuilderCometSupply.t.sol @@ -38,6 +38,14 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { internal pure returns (QuarkBuilder.CometSupplyIntent memory) + { + return cometSupply_(chainId, amount, address(0xa11ce)); + } + + function cometSupply_(uint256 chainId, uint256 amount, address sender) + internal + pure + returns (QuarkBuilder.CometSupplyIntent memory) { return QuarkBuilder.CometSupplyIntent({ amount: amount, @@ -45,7 +53,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { blockTimestamp: BLOCK_TIMESTAMP, chainId: chainId, comet: COMET, - sender: address(0xa11ce) + sender: sender }); } @@ -121,6 +129,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -130,6 +140,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -145,7 +157,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { "action context encoded from SupplyActionContext" ); - // // TODO: Check the contents of the EIP712 data + // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -156,7 +168,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](1); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(3e6)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -201,6 +213,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -210,6 +224,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -225,7 +241,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { "action context encoded from SupplyActionContext" ); - // // TODO: Check the contents of the EIP712 data + // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -260,7 +276,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { }); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -297,6 +313,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -306,6 +324,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -321,7 +341,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { "action context encoded from SupplyActionContext" ); - // // TODO: Check the contents of the EIP712 data + // TODO: Check the contents of the EIP712 data assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -362,6 +382,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -371,6 +393,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -395,7 +419,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { function testCometSupplyWithBridge() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.cometSupply( - cometSupply_(8453, 5e6), + // We need to set Bob as the sender because only he has an account on chain 8453 + cometSupply_(8453, 5e6, address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -433,15 +458,17 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -472,6 +499,9 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + // TODO: might need to adjust intent to supply with Bob + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -482,6 +512,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -491,7 +523,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -501,11 +533,13 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "second action is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SUPPLY", "action type is 'SUPPLY'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -530,7 +564,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { function testCometSupplyMaxWithBridge() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.cometSupply( - cometSupply_(8453, type(uint256).max), + // We need to set Bob as the sender because only he has an account on chain 8453 + cometSupply_(8453, type(uint256).max, address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -568,15 +603,17 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -607,6 +644,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -617,6 +656,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -626,7 +667,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -636,11 +677,13 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "second action is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SUPPLY", "action type is 'SUPPLY'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -670,7 +713,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.cometSupply( - cometSupply_(8453, type(uint256).max), + // We need to set Bob as the sender because only he has an account on chain 8453 + cometSupply_(8453, type(uint256).max, address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -699,16 +743,18 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.5e6, // 3e6 - 0.5e6 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -729,6 +775,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -739,6 +787,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -748,7 +798,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -757,11 +807,13 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { ); // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SUPPLY", "action type is 'SUPPLY'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -791,7 +843,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.cometSupply( - cometSupply_(8453, 5e6), + // We need to set Bob as the sender because only he has an account on chain 8453 + cometSupply_(8453, 5e6, address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -820,16 +873,18 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -850,6 +905,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -860,6 +917,8 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -869,7 +928,7 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -878,11 +937,13 @@ contract QuarkBuilderCometSupplyTest is Test, QuarkBuilderTest { ); // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SUPPLY", "action type is 'SUPPLY'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( diff --git a/test/builder/QuarkBuilderCometWithdraw.t.sol b/test/builder/QuarkBuilderCometWithdraw.t.sol index 88f6de1a..77c7282f 100644 --- a/test/builder/QuarkBuilderCometWithdraw.t.sol +++ b/test/builder/QuarkBuilderCometWithdraw.t.sol @@ -18,13 +18,29 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { pure returns (QuarkBuilder.CometWithdrawIntent memory) { + return cometWithdraw_({ + chainId: chainId, + comet: comet, + assetSymbol: assetSymbol, + amount: amount, + withdrawer: address(0xa11ce) + }); + } + + function cometWithdraw_( + uint256 chainId, + address comet, + string memory assetSymbol, + uint256 amount, + address withdrawer + ) internal pure returns (QuarkBuilder.CometWithdrawIntent memory) { return QuarkBuilder.CometWithdrawIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, chainId: chainId, comet: comet, - withdrawer: address(0xa11ce) + withdrawer: withdrawer }); } @@ -70,6 +86,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -79,6 +97,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -135,6 +155,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -144,6 +166,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -200,6 +224,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -209,6 +235,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -253,7 +281,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](2); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), ALICE_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 3e6), // 3 USDC on mainnet cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -261,7 +289,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), BOB_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 0), // 0 USDC on base cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -269,7 +297,16 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { }); QuarkBuilder.BuilderResult memory result = builder.cometWithdraw( - cometWithdraw_(8453, cometUsdc_(8453), "LINK", 5e18), chainAccountsList, paymentUsdc_(maxCosts) + // We need to set Bob as the withdrawer because only he has an account on chain 8453 + cometWithdraw_({ + chainId: 8453, + comet: cometUsdc_(8453), + assetSymbol: "LINK", + amount: 5e18, + withdrawer: address(0xb0b) + }), + chainAccountsList, + paymentUsdc_(maxCosts) ); address paycallAddress = paycallUsdc_(1); @@ -296,16 +333,18 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -326,6 +365,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -336,6 +377,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -346,7 +389,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { chainId: 1, destinationChainId: 8453, price: USDC_PRICE, - recipient: address(0xa11ce), + recipient: address(0xb0b), token: USDC_1 }) ), @@ -354,11 +397,13 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { ); // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "WITHDRAW", "action type is 'WITHDRAW'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 1e6, "payment should have max cost of 1e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -397,7 +442,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: cometPortfolios, @@ -439,6 +484,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -448,6 +495,8 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -486,7 +535,7 @@ contract QuarkBuilderCometWithdrawTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: bytes32(uint256(12)), assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: cometPortfolios, diff --git a/test/builder/QuarkBuilderMorphoBorrow.t.sol b/test/builder/QuarkBuilderMorphoBorrow.t.sol index dad503e1..84810b5c 100644 --- a/test/builder/QuarkBuilderMorphoBorrow.t.sol +++ b/test/builder/QuarkBuilderMorphoBorrow.t.sol @@ -25,12 +25,30 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { uint256 amount, string memory collateralAssetSymbol, uint256 collateralAmount + ) internal pure returns (QuarkBuilder.MorphoBorrowIntent memory) { + return borrowIntent_({ + chainId: chainId, + assetSymbol: assetSymbol, + amount: amount, + collateralAssetSymbol: collateralAssetSymbol, + collateralAmount: collateralAmount, + borrower: address(0xa11ce) + }); + } + + function borrowIntent_( + uint256 chainId, + string memory assetSymbol, + uint256 amount, + string memory collateralAssetSymbol, + uint256 collateralAmount, + address borrower ) internal pure returns (QuarkBuilder.MorphoBorrowIntent memory) { return QuarkBuilder.MorphoBorrowIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, - borrower: address(0xa11ce), + borrower: borrower, chainId: chainId, collateralAmount: collateralAmount, collateralAssetSymbol: collateralAssetSymbol @@ -43,7 +61,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 1e18), cometPortfolios: emptyCometPortfolios_(), @@ -53,7 +71,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -85,7 +103,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 0), // user has 1 WBTC cometPortfolios: emptyCometPortfolios_(), @@ -95,7 +113,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -129,6 +147,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -138,7 +158,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -158,6 +179,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -168,7 +190,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -178,7 +200,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xa11ce), - nextNonce: 2, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "ETH", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 10e18, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -229,6 +251,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -238,7 +262,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -258,6 +283,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -268,7 +294,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 1e8, 0), // user has 1 WBTC and 1USDC for payment cometPortfolios: emptyCometPortfolios_(), @@ -278,7 +304,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -331,6 +357,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -340,7 +368,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); - + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -360,6 +389,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -370,7 +400,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e8, 0), // user has 1 WBTC but with 0 USDC cometPortfolios: emptyCometPortfolios_(), @@ -380,7 +410,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -433,6 +463,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -442,6 +474,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -461,6 +495,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -471,7 +506,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 2, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "cbETH", "WETH"), assetBalances: Arrays.uintArray(5e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -481,7 +516,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 0, 1e18, 0), cometPortfolios: emptyCometPortfolios_(), @@ -495,7 +530,14 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.morphoBorrow( - borrowIntent_(8453, "WETH", 0.2e18, "cbETH", 1e18), + borrowIntent_({ + chainId: 8453, + assetSymbol: "WETH", + amount: 0.2e18, + collateralAssetSymbol: "cbETH", + collateralAmount: 1e18, + borrower: address(0xb0b) + }), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) ); @@ -525,12 +567,12 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); @@ -541,6 +583,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -571,6 +615,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -581,6 +627,8 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -590,7 +638,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -599,12 +647,13 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ); // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_BORROW", "action type is 'MORPHO_BORROW'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 1e6, "payment should have max cost of 1e6"); - + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -624,6 +673,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoBorrowActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -638,7 +688,7 @@ contract QuarkBuilderMorphoBorrowTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 2e8, 1e18), // user does not have enough USDC cometPortfolios: emptyCometPortfolios_(), diff --git a/test/builder/QuarkBuilderMorphoRepay.t.sol b/test/builder/QuarkBuilderMorphoRepay.t.sol index 7f400ef5..c6ec8a5e 100644 --- a/test/builder/QuarkBuilderMorphoRepay.t.sol +++ b/test/builder/QuarkBuilderMorphoRepay.t.sol @@ -25,12 +25,30 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { uint256 amount, string memory collateralAssetSymbol, uint256 collateralAmount + ) internal pure returns (QuarkBuilder.MorphoRepayIntent memory) { + return repayIntent_({ + chainId: chainId, + assetSymbol: assetSymbol, + amount: amount, + collateralAssetSymbol: collateralAssetSymbol, + collateralAmount: collateralAmount, + repayer: address(0xa11ce) + }); + } + + function repayIntent_( + uint256 chainId, + string memory assetSymbol, + uint256 amount, + string memory collateralAssetSymbol, + uint256 collateralAmount, + address repayer ) internal pure returns (QuarkBuilder.MorphoRepayIntent memory) { return QuarkBuilder.MorphoRepayIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, - repayer: address(0xa11ce), + repayer: repayer, chainId: chainId, collateralAmount: collateralAmount, collateralAssetSymbol: collateralAssetSymbol @@ -58,7 +76,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 8453, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0.4e6, 0, 0, 1e18), // user does not have enough USDC cometPortfolios: emptyCometPortfolios_(), @@ -80,7 +98,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(1e6, 0, 0, 0), // has 1 USDC cometPortfolios: emptyCometPortfolios_(), @@ -90,7 +108,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -112,12 +130,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); assertEq(result.paymentCurrency, "usd", "usd currency"); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one operation"); assertEq( result.quarkOperations[0].scriptAddress, - MorphoActionsAddress, + morphoActionsAddress, "script address is correct given the code jar address on mainnet" ); assertEq( @@ -133,6 +151,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -142,7 +162,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -162,6 +183,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -172,7 +194,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "ETH", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -182,7 +204,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xa11ce), - nextNonce: 2, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "ETH", "cbETH", "WETH"), assetBalances: Arrays.uintArray(0, 1e18, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -207,7 +229,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address multicallAddress = CodeJarHelper.getCodeAddress(type(Multicall).creationCode); address wrapperActionsAddress = CodeJarHelper.getCodeAddress(type(WrapperActions).creationCode); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one merged operation"); @@ -218,7 +240,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); address[] memory callContracts = new address[](2); callContracts[0] = wrapperActionsAddress; - callContracts[1] = MorphoActionsAddress; + callContracts[1] = morphoActionsAddress; bytes[] memory callDatas = new bytes[](2); callDatas[0] = abi.encodeWithSelector( WrapperActions.wrapETH.selector, TokenWrapper.getKnownWrapperTokenPair(8453, "WETH").wrapper, 1e18 @@ -231,7 +253,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector(Multicall.run.selector, callContracts, callDatas), - "calldata is Multicall.run([wrapperActionsAddress, MorphoActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 0, 0e18, address(0xa11ce), address(0xa11ce))" + "calldata is Multicall.run([wrapperActionsAddress, morphoActionsAddress], [WrapperActions.wrapWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 1e18), MorphoActions.repayAndWithdrawCollateral(MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, WETH, USDC), 1e18, 0, 0e18, address(0xa11ce), address(0xa11ce))" ); assertEq(result.quarkOperations[0].scriptSources.length, 3); assertEq(result.quarkOperations[0].scriptSources[0], type(WrapperActions).creationCode); @@ -240,6 +262,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -249,7 +273,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); - + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -269,6 +294,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -279,7 +305,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(2e6, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -289,7 +315,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -313,7 +339,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { paymentUsdc_(maxCosts) // and paying with USDC ); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); address paycallAddress = paycallUsdc_(1); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -329,7 +355,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - MorphoActionsAddress, + morphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, (MorphoInfo.getMorphoAddress(1), MorphoInfo.getMarketParams(1, "WBTC", "USDC"), 1e6, 0e8) @@ -347,6 +373,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -356,6 +384,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); uint256[] memory collateralAmounts = new uint256[](1); collateralAmounts[0] = 0e18; @@ -385,6 +415,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -401,7 +432,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(4e6, 0, 0, 0), // 4 USDC on mainnet cometPortfolios: emptyCometPortfolios_(), @@ -411,7 +442,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[1] = ChainPortfolio({ chainId: 8453, account: address(0xb0b), - nextNonce: 2, + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // no assets on base cometPortfolios: emptyCometPortfolios_(), @@ -425,7 +456,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { "USDC", // repaying 2 USDC, bridged from mainnet to base 2e6, "WETH", - 0e18 + 0e18, + address(0xb0b) ), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) @@ -434,7 +466,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address paycallAddress = paycallUsdc_(1); address paycallAddressBase = paycallUsdc_(8453); address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); assertEq(result.paymentCurrency, "usdc", "usdc currency"); @@ -456,12 +488,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.2e6, // 2e6 repaid + 0.2e6 max cost on Base 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 2.2e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 2.2e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); @@ -472,6 +504,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -484,7 +518,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - MorphoActionsAddress, + morphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, (MorphoInfo.getMorphoAddress(8453), MorphoInfo.getMarketParams(8453, "WETH", "USDC"), 2e6, 0e18) @@ -502,6 +536,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -512,6 +548,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -522,7 +560,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainId: 1, destinationChainId: 8453, price: USDC_PRICE, - recipient: address(0xa11ce), + recipient: address(0xb0b), token: usdc_(1) }) ), @@ -530,12 +568,13 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ); // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_REPAY", "action type is 'MORPHO_REPAY'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.2e6, "payment should have max cost of 0.2e6"); - + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -555,6 +594,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -577,7 +617,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(20e6, 0, 0, 0), // has 20 USDC cometPortfolios: emptyCometPortfolios_(), @@ -603,7 +643,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address paycallAddress = CodeJarHelper.getCodeAddress( abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) ); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); // Check the quark operations assertEq(result.quarkOperations.length, 1, "one operation"); @@ -617,7 +657,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[0].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - MorphoActionsAddress, + morphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, ( @@ -641,6 +681,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -650,7 +692,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); - + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -670,6 +713,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); @@ -693,7 +737,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(50e6, 0, 0, 0), // has 50 USDC cometPortfolios: emptyCometPortfolios_(), @@ -702,8 +746,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { }); chainPortfolios[1] = ChainPortfolio({ chainId: 8453, - account: address(0xa11ce), - nextNonce: 12, + account: address(0xb0b), + nonceSecret: BOB_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "WBTC", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), // has 0 USDC on base cometPortfolios: emptyCometPortfolios_(), @@ -718,7 +762,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { "USDC", type(uint256).max, // repaying max (all 10 USDC) "WETH", - 0 + 0, + address(0xb0b) ), chainAccountsFromChainPortfolios(chainPortfolios), paymentUsdc_(maxCosts) @@ -727,7 +772,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.paymentCurrency, "usdc", "usdc currency"); address cctpBridgeActionsAddress = CodeJarHelper.getCodeAddress(type(CCTPBridgeActions).creationCode); - address MorphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); + address morphoActionsAddress = CodeJarHelper.getCodeAddress(type(MorphoActions).creationCode); address paycallAddress = CodeJarHelper.getCodeAddress( abi.encodePacked(type(Paycall).creationCode, abi.encode(ETH_USD_PRICE_FEED_1, USDC_1)) ); @@ -753,12 +798,12 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 10.11e6, // 10e6 repaid + .1% buffer + 0.1e6 max cost on Base 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 10.11e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 10.11e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq(result.quarkOperations[0].scriptSources.length, 2); assertEq(result.quarkOperations[0].scriptSources[0], type(CCTPBridgeActions).creationCode); @@ -769,6 +814,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -780,7 +827,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( Paycall.run.selector, - MorphoActionsAddress, + morphoActionsAddress, abi.encodeCall( MorphoActions.repayAndWithdrawCollateral, ( @@ -804,6 +851,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -813,6 +862,8 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -823,7 +874,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { chainId: 1, destinationChainId: 8453, price: USDC_PRICE, - recipient: address(0xa11ce), + recipient: address(0xb0b), token: USDC_1 }) ), @@ -832,12 +883,13 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_REPAY", "action type is 'MORPHO_REPAY'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); - + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -857,6 +909,7 @@ contract QuarkBuilderMorphoRepayTest is Test, QuarkBuilderTest { ), "action context encoded from MorphoRepayActionContext" ); + assertNotEq(result.eip712Data.digest, hex"", "non-empty digest"); assertNotEq(result.eip712Data.domainSeparator, hex"", "non-empty domain separator"); assertNotEq(result.eip712Data.hashStruct, hex"", "non-empty hashStruct"); diff --git a/test/builder/QuarkBuilderMorphoVaultSupply.t.sol b/test/builder/QuarkBuilderMorphoVaultSupply.t.sol index fe44a489..fc59016e 100644 --- a/test/builder/QuarkBuilderMorphoVaultSupply.t.sol +++ b/test/builder/QuarkBuilderMorphoVaultSupply.t.sol @@ -23,13 +23,22 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { internal pure returns (QuarkBuilder.MorphoVaultSupplyIntent memory) + { + return + morphoSupplyIntent_({chainId: chainId, amount: amount, assetSymbol: assetSymbol, sender: address(0xa11ce)}); + } + + function morphoSupplyIntent_(uint256 chainId, uint256 amount, string memory assetSymbol, address sender) + internal + pure + returns (QuarkBuilder.MorphoVaultSupplyIntent memory) { return QuarkBuilder.MorphoVaultSupplyIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, chainId: chainId, - sender: address(0xa11ce) + sender: sender }); } @@ -71,7 +80,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](3); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 0e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -79,7 +88,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 0e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -87,12 +96,13 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, - quarkStates: quarkStates_(address(0xc0b), 5), + quarkSecrets: quarkSecrets_(address(0xc0b), bytes32(uint256(5))), assetPositionsList: assetPositionsList_(7777, address(0xc0b), 100e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), morphoVaultPositions: emptyMorphoVaultPositions_() }); + vm.expectRevert(abi.encodeWithSelector(QuarkBuilder.FundsUnavailable.selector, "USDC", 2e6, 0)); builder.morphoVaultSupply( // there is no bridge to brige from 7777, so we cannot get to our funds @@ -139,6 +149,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -148,6 +160,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -174,7 +188,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](1); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(3e6)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -210,6 +224,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -219,6 +235,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -268,7 +286,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { }); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -306,6 +324,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -315,6 +335,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -373,6 +395,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -382,6 +406,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -406,7 +432,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { function testMorphoVaultSupplyWithBridge() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.morphoVaultSupply( - morphoSupplyIntent_(8453, 5e6, "USDC"), + morphoSupplyIntent_(8453, 5e6, "USDC", address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -429,15 +455,17 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -455,6 +483,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -465,6 +495,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -474,7 +506,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -484,11 +516,13 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "second action is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_SUPPLY", "action type is 'MORPHO_VAULT_SUPPLY'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -513,7 +547,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { function testMorphoVaultSupplyMaxWithBridge() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.morphoVaultSupply( - morphoSupplyIntent_(8453, type(uint256).max, "USDC"), + morphoSupplyIntent_(8453, type(uint256).max, "USDC", address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -536,15 +570,17 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -562,6 +598,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // check the actions // first action @@ -572,6 +610,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -581,7 +621,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -591,11 +631,13 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { // second action assertEq(result.actions[1].chainId, 8453, "second action is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_SUPPLY", "action type is 'MORPHO_VAULT_SUPPLY'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -625,7 +667,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.morphoVaultSupply( - morphoSupplyIntent_(8453, type(uint256).max, "USDC"), + morphoSupplyIntent_(8453, type(uint256).max, "USDC", address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -654,16 +696,18 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.5e6, // 3e6 - 0.5e6 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -686,6 +730,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -696,6 +742,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -705,7 +753,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -714,11 +762,13 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { ); // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_SUPPLY", "action type is 'MORPHO_VAULT_SUPPLY'"); assertEq(result.actions[1].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -748,7 +798,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.morphoVaultSupply( - morphoSupplyIntent_(8453, 5e6, "USDC"), + morphoSupplyIntent_(8453, 5e6, "USDC", address(0xb0b)), chainAccountsList_(6e6), // holding 3 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -777,16 +827,18 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -809,6 +861,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -819,6 +873,8 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -828,7 +884,7 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -837,11 +893,13 @@ contract QuarkBuilderMorphoVaultTest is Test, QuarkBuilderTest { ); // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_SUPPLY", "action type is 'MORPHO_VAULT_SUPPLY'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( diff --git a/test/builder/QuarkBuilderMorphoVaultWithdraw.t.sol b/test/builder/QuarkBuilderMorphoVaultWithdraw.t.sol index 5dfa671d..23eb6a57 100644 --- a/test/builder/QuarkBuilderMorphoVaultWithdraw.t.sol +++ b/test/builder/QuarkBuilderMorphoVaultWithdraw.t.sol @@ -19,13 +19,26 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { internal pure returns (QuarkBuilder.MorphoVaultWithdrawIntent memory) + { + return morphoWithdrawIntent_({ + amount: amount, + assetSymbol: assetSymbol, + chainId: chainId, + withdrawer: address(0xa11ce) + }); + } + + function morphoWithdrawIntent_(uint256 chainId, uint256 amount, string memory assetSymbol, address withdrawer) + internal + pure + returns (QuarkBuilder.MorphoVaultWithdrawIntent memory) { return QuarkBuilder.MorphoVaultWithdrawIntent({ amount: amount, assetSymbol: assetSymbol, blockTimestamp: BLOCK_TIMESTAMP, chainId: chainId, - withdrawer: address(0xa11ce) + withdrawer: withdrawer }); } @@ -56,6 +69,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -65,6 +80,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -121,6 +138,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -130,6 +149,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to .1e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -186,6 +207,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -195,6 +218,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment max is set to .5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -239,7 +264,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](2); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), ALICE_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 3e6), // 3 USDC on mainnet cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -247,7 +272,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), BOB_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 0), // 0 USDC on base cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -255,7 +280,9 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { }); QuarkBuilder.BuilderResult memory result = builder.morphoVaultWithdraw( - morphoWithdrawIntent_(8453, 1e18, "WETH"), chainAccountsList, paymentUsdc_(maxCosts) + morphoWithdrawIntent_({chainId: 8453, amount: 1e18, assetSymbol: "WETH", withdrawer: address(0xb0b)}), + chainAccountsList, + paymentUsdc_(maxCosts) ); address paycallAddress = paycallUsdc_(1); @@ -282,16 +309,18 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.1e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xa11ce, USDC_1)), 0.1e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(0xBd3fa81B58Ba92a82136038B25aDec7066af3155, 1e6, 6, 0xb0b, USDC_1)), 0.1e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // second operation assertEq( @@ -312,6 +341,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -322,6 +353,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment should have max cost of 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -332,7 +365,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { chainId: 1, destinationChainId: 8453, price: USDC_PRICE, - recipient: address(0xa11ce), + recipient: address(0xb0b), token: USDC_1 }) ), @@ -340,11 +373,13 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { ); // second action assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "MORPHO_VAULT_WITHDRAW", "action type is 'MORPHO_VAULT_WITHDRAW'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 1e6, "payment should have max cost of 1e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -381,7 +416,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), @@ -423,6 +458,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -432,6 +469,8 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 0.1e6, "payment max is set to 0.1e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -468,7 +507,7 @@ contract QuarkBuilderMorphoVaultWithdrawTest is Test, QuarkBuilderTest { chainPortfolios[0] = ChainPortfolio({ chainId: 1, account: address(0xa11ce), - nextNonce: 12, + nonceSecret: ALICE_DEFAULT_SECRET, assetSymbols: Arrays.stringArray("USDC", "USDT", "LINK", "WETH"), assetBalances: Arrays.uintArray(0, 0, 0, 0), cometPortfolios: emptyCometPortfolios_(), diff --git a/test/builder/QuarkBuilderRecurringSwap.t.sol b/test/builder/QuarkBuilderRecurringSwap.t.sol index d79c3402..0f720506 100644 --- a/test/builder/QuarkBuilderRecurringSwap.t.sol +++ b/test/builder/QuarkBuilderRecurringSwap.t.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import {QuarkBuilderTest} from "test/builder/lib/QuarkBuilderTest.sol"; +import {ReplayableHelper} from "test/builder/lib/ReplayableHelper.sol"; import {RecurringSwap} from "src/RecurringSwap.sol"; import {CCTPBridgeActions} from "src/BridgeScripts.sol"; @@ -190,7 +191,7 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { buyAmount: 1e18, isExactOut: false, interval: 86_400, - sender: address(0xfe11a), + sender: address(0xa11ce), blockTimestamp: BLOCK_TIMESTAMP }); QuarkBuilder.BuilderResult memory result = builder.recurringSwap( @@ -229,15 +230,23 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { "calldata is RecurringSwap.swap(SwapConfig(...));" ); assertEq(result.quarkOperations[0].expiry, type(uint256).max, "expiry is type(uint256).max"); + assertEq( + result.quarkOperations[0].nonce, + ReplayableHelper.generateNonceFromSecret(ALICE_DEFAULT_SECRET, Actions.RECURRING_SWAP_TOTAL_PLAYS), + "unexpected nonce" + ); + assertEq(result.quarkOperations[0].isReplayable, true, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - assertEq(result.actions[0].quarkAccount, address(0xfe11a), "0xfe11a does the swap"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce does the swap"); assertEq(result.actions[0].actionType, "RECURRING_SWAP", "action type is 'RECURRING_SWAP'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, Actions.RECURRING_SWAP_TOTAL_PLAYS, "total plays is correct"); assertEq( result.actions[0].actionContext, abi.encode( @@ -273,7 +282,7 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { buyAmount: 1e18, isExactOut: true, interval: 86_400, - sender: address(0xfe11a), + sender: address(0xa11ce), blockTimestamp: BLOCK_TIMESTAMP }); QuarkBuilder.BuilderResult memory result = builder.recurringSwap( @@ -312,15 +321,23 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { "calldata is RecurringSwap.swap(SwapConfig(...));" ); assertEq(result.quarkOperations[0].expiry, type(uint256).max, "expiry is type(uint256).max"); + assertEq( + result.quarkOperations[0].nonce, + ReplayableHelper.generateNonceFromSecret(ALICE_DEFAULT_SECRET, Actions.RECURRING_SWAP_TOTAL_PLAYS), + "unexpected nonce" + ); + assertEq(result.quarkOperations[0].isReplayable, true, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - assertEq(result.actions[0].quarkAccount, address(0xfe11a), "0xfe11a does the swap"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce does the swap"); assertEq(result.actions[0].actionType, "RECURRING_SWAP", "action type is 'RECURRING_SWAP'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, Actions.RECURRING_SWAP_TOTAL_PLAYS, "total plays is correct"); assertEq( result.actions[0].actionContext, abi.encode( @@ -356,7 +373,7 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { buyAmount: 1e18, isExactOut: false, interval: 86_400, - sender: address(0xfe11a), + sender: address(0xa11ce), blockTimestamp: BLOCK_TIMESTAMP }); PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1); @@ -392,15 +409,23 @@ contract QuarkBuilderRecurringSwapTest is Test, QuarkBuilderTest { "calldata is Paycall.run(RecurringSwap.swap(SwapConfig(...)), 5e6);" ); assertEq(result.quarkOperations[0].expiry, type(uint256).max, "expiry is type(uint256).max"); + assertEq( + result.quarkOperations[0].nonce, + ReplayableHelper.generateNonceFromSecret(ALICE_DEFAULT_SECRET, Actions.RECURRING_SWAP_TOTAL_PLAYS), + "unexpected nonce" + ); + assertEq(result.quarkOperations[0].isReplayable, true, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); - assertEq(result.actions[0].quarkAccount, address(0xfe11a), "0xfe11a does the swap"); + assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce does the swap"); assertEq(result.actions[0].actionType, "RECURRING_SWAP", "action type is 'RECURRING_SWAP'"); assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 5e6, "payment max is set to 5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, Actions.RECURRING_SWAP_TOTAL_PLAYS, "total plays is correct"); assertEq( result.actions[0].actionContext, abi.encode( diff --git a/test/builder/QuarkBuilderSwap.t.sol b/test/builder/QuarkBuilderSwap.t.sol index 18b5681c..e36b9c5e 100644 --- a/test/builder/QuarkBuilderSwap.t.sol +++ b/test/builder/QuarkBuilderSwap.t.sol @@ -181,6 +181,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -190,6 +192,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -248,7 +252,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { }); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -288,6 +292,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -297,6 +303,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -365,6 +373,9 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); @@ -373,6 +384,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 5e6, "payment max is set to 5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -409,7 +422,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](3); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(9005e6)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -417,7 +430,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(8453, address(0xb0b), uint256(0)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -425,7 +438,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, - quarkStates: quarkStates_(address(0xc0b), 5), + quarkSecrets: quarkSecrets_(address(0xc0b), bytes32(uint256(5))), assetPositionsList: assetPositionsList_(7777, address(0xc0b), uint256(0)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -467,6 +480,9 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); @@ -475,6 +491,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 5e6, "payment max is set to 5e6 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -507,7 +525,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { function testBridgeSwapSucceeds() public { QuarkBuilder builder = new QuarkBuilder(); QuarkBuilder.BuilderResult memory result = builder.swap( - buyWeth_(8453, usdc_(8453), 3000e6, 1e18, address(0xa11ce), BLOCK_TIMESTAMP), // swap 3000 USDC on chain 8453 to 1 WETH + buyWeth_(8453, usdc_(8453), 3000e6, 1e18, address(0xb0b), BLOCK_TIMESTAMP), // swap 3000 USDC on chain 8453 to 1 WETH chainAccountsList_(4000e6), // holding 4000 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -544,15 +562,18 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1000e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, address( @@ -583,6 +604,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -592,6 +615,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -601,7 +626,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -609,11 +634,13 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SWAP", "action type is 'SWAP'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -650,7 +677,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 1e6}); // Note: There are 2000e6 USDC on each chain, so the Builder should attempt to bridge 1000 + 1 (for payment) USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.swap( - buyWeth_(8453, usdc_(8453), 3000e6, 1e18, address(0xa11ce), BLOCK_TIMESTAMP), // swap 3000 USDC on chain 8453 to 1 WETH + buyWeth_(8453, usdc_(8453), 3000e6, 1e18, address(0xb0b), BLOCK_TIMESTAMP), // swap 3000 USDC on chain 8453 to 1 WETH chainAccountsList_(4000e6), // holding 4000 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -683,16 +710,19 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1001e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 5e5);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e5);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, paycallAddressBase, @@ -719,6 +749,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -728,6 +760,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 5e6, "payment should have max cost of 5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -737,7 +771,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -745,11 +779,13 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SWAP", "action type is 'SWAP'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 1e6, "payment should have max cost of 1e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -785,7 +821,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 5e6}); maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 1e6}); QuarkBuilder.BuilderResult memory result = builder.swap( - buyWeth_(8453, usdc_(8453), type(uint256).max, 2e18, address(0xa11ce), BLOCK_TIMESTAMP), // swap max on chain 8453 to 4 WETH + buyWeth_(8453, usdc_(8453), type(uint256).max, 2e18, address(0xb0b), BLOCK_TIMESTAMP), // swap max on chain 8453 to 4 WETH chainAccountsList_(6010e6), // holding 6010 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -818,16 +854,19 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3000e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 5e6 ), - "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3000e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 5e5);" + "calldata is Quotecall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 3000e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e5);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, quotecallAddressBase, @@ -854,6 +893,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -863,6 +904,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 5e6, "payment should have max cost of 5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -872,7 +915,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -880,11 +923,13 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SWAP", "action type is 'SWAP'"); assertEq(result.actions[1].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 1e6, "payment should have max cost of 1e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -921,7 +966,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { maxCosts[1] = PaymentInfo.PaymentMaxCost({chainId: 8453, amount: 3500e6}); // Note: There are 3000e6 USDC on each chain, so the Builder should attempt to bridge 500 USDC to chain 8453 to cover the max cost QuarkBuilder.BuilderResult memory result = builder.swap( - buyWeth_(8453, usdt_(8453), 3000e6, 1e18, address(0xa11ce), BLOCK_TIMESTAMP), // swap 3000 USDT on chain 8453 to 1 WETH + buyWeth_(8453, usdt_(8453), 3000e6, 1e18, address(0xb0b), BLOCK_TIMESTAMP), // swap 3000 USDT on chain 8453 to 1 WETH chainAccountsList_(6000e6), // holding 6000 USDC and USDT in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -954,16 +999,19 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 500e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 500e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 5e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 500e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptCalldata, abi.encodeWithSelector( @@ -990,6 +1038,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 3 days, "expiry is current blockTimestamp + 3 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -999,6 +1049,8 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 5e6, "payment should have max cost of 5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1008,7 +1060,7 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -1016,11 +1068,13 @@ contract QuarkBuilderSwapTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "SWAP", "action type is 'SWAP'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 3500e6, "payment should have max cost of 3500e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( diff --git a/test/builder/QuarkBuilderTransfer.t.sol b/test/builder/QuarkBuilderTransfer.t.sol index dfb3b94f..59ea4b60 100644 --- a/test/builder/QuarkBuilderTransfer.t.sol +++ b/test/builder/QuarkBuilderTransfer.t.sol @@ -52,7 +52,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address recipient, uint256 blockTimestamp ) internal pure returns (QuarkBuilder.TransferIntent memory) { - return QuarkBuilder.TransferIntent({ + return transferToken_({ chainId: chainId, sender: address(0xa11ce), recipient: recipient, @@ -62,6 +62,24 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); } + function transferToken_( + string memory assetSymbol, + uint256 chainId, + uint256 amount, + address sender, + address recipient, + uint256 blockTimestamp + ) internal pure returns (QuarkBuilder.TransferIntent memory) { + return QuarkBuilder.TransferIntent({ + chainId: chainId, + sender: sender, + recipient: recipient, + amount: amount, + assetSymbol: assetSymbol, + blockTimestamp: blockTimestamp + }); + } + function testInsufficientFunds() public { QuarkBuilder builder = new QuarkBuilder(); vm.expectRevert(abi.encodeWithSelector(QuarkBuilder.FundsUnavailable.selector, "USDC", 10e6, 0e6)); @@ -147,6 +165,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "one action"); @@ -156,6 +176,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -212,6 +234,9 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); @@ -220,6 +245,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 1e5, "payment max is set to 1e5 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -245,7 +272,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { QuarkBuilder builder = new QuarkBuilder(); // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.transfer( - transferUsdc_(8453, 5e6, address(0xceecee), BLOCK_TIMESTAMP), // transfer 5 USDC on chain 8453 to 0xceecee + transferToken_({ + assetSymbol: "USDC", + chainId: 8453, + amount: 5e6, + sender: address(0xb0b), + recipient: address(0xceecee), + blockTimestamp: BLOCK_TIMESTAMP + }), // transfer 5 USDC on chain 8453 to 0xceecee chainAccountsList_(6e6), // holding 6 USDC in total across chains 1, 8453 paymentUsd_() ); @@ -282,15 +316,18 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ) ), - "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1)));" + "calldata is CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1)));" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, address( @@ -319,6 +356,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "two actions"); @@ -328,6 +367,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is null"); assertEq(result.actions[0].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -337,7 +378,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -345,11 +386,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[1].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[1].paymentToken, address(0), "payment token is null"); assertEq(result.actions[1].paymentMaxCost, 0, "payment has no max cost, since 'OFFCHAIN'"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -379,7 +422,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 2 USDC to chain 8453 QuarkBuilder.BuilderResult memory result = builder.transfer( - transferUsdc_(8453, 5e6, address(0xceecee), BLOCK_TIMESTAMP), // transfer 5 USDC on chain 8453 to 0xceecee + transferToken_({ + assetSymbol: "USDC", + chainId: 8453, + amount: 5e6, + sender: address(0xb0b), + recipient: address(0xceecee), + blockTimestamp: BLOCK_TIMESTAMP + }), // transfer 5 USDC on chain 8453 to 0xceecee chainAccountsList_(6e6), // holding 6 USDC in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -406,16 +456,19 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 5e5);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e5);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, paycallAddressBase, @@ -434,6 +487,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -443,6 +498,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 5e5"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -452,7 +509,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -460,11 +517,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 1e5"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -494,7 +553,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { // Note: There are 3e6 USDC on each chain, so the Builder should attempt to bridge 1.5 USDC to chain 8453 to pay for the txn QuarkBuilder.BuilderResult memory result = builder.transfer( - transferToken_("USDT", 8453, 3e6, address(0xceecee), BLOCK_TIMESTAMP), // transfer 3 USDT on chain 8453 to 0xceecee + transferToken_({ + assetSymbol: "USDT", + chainId: 8453, + amount: 3e6, + sender: address(0xb0b), + recipient: address(0xceecee), + blockTimestamp: BLOCK_TIMESTAMP + }), // transfer 3 USDT on chain 8453 to 0xceecee chainAccountsList_(6e6), // holding 6 USDC and USDT in total across chains 1, 8453 paymentUsdc_(maxCosts) ); @@ -521,16 +587,19 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1.5e6, 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1.5e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1.5e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, paycallAddressBase, @@ -549,6 +618,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -558,6 +629,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 0.5e6"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -567,7 +640,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -575,11 +648,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 4.5e6, "payment should have max cost of 4.5e6"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -607,7 +682,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](1); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(10e6)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -648,6 +723,9 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + // check the actions assertEq(result.actions.length, 1, "one action"); assertEq(result.actions[0].chainId, 1, "operation is on chainid 1"); @@ -656,6 +734,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); assertEq(result.actions[0].paymentMaxCost, 1e5, "payment max is set to 1e5 in this test case"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -685,7 +765,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](2); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 8e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -693,7 +773,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 4e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -701,7 +781,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); QuarkBuilder.BuilderResult memory result = builder.transfer( - transferUsdc_(8453, type(uint256).max, address(0xceecee), BLOCK_TIMESTAMP), // transfer max USDC on chain 8453 to 0xceecee + transferToken_({ + assetSymbol: "USDC", + chainId: 8453, + amount: type(uint256).max, + sender: address(0xb0b), + recipient: address(0xceecee), + blockTimestamp: BLOCK_TIMESTAMP + }), // transfer max USDC on chain 8453 to 0xceecee chainAccountsList, // holding 8 USDC on chains 1, and 4 USDC on 8453 paymentUsdc_(maxCosts) ); @@ -732,16 +819,19 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 7.5e6, // 8e6 (holdings on mainnet) - 0.5e6 (max cost on mainnet) 6, - bytes32(uint256(uint160(0xa11ce))), + bytes32(uint256(uint160(0xb0b))), usdc_(1) ), 0.5e6 ), - "calldata is Quote.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 7.5e6, 6, bytes32(uint256(uint160(0xa11ce))), usdc_(1))), 0.5e6);" + "calldata is Quote.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 7.5e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);" ); assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); + assertEq( result.quarkOperations[1].scriptAddress, quotecallAddressBase, @@ -763,6 +853,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[1].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[1].nonce, BOB_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[1].isReplayable, false, "isReplayable is false"); // Check the actions assertEq(result.actions.length, 2, "one action"); @@ -772,6 +864,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet"); assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 5e5"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -781,7 +875,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { token: USDC_1, assetSymbol: "USDC", chainId: 1, - recipient: address(0xa11ce), + recipient: address(0xb0b), destinationChainId: 8453, bridgeType: Actions.BRIDGE_TYPE_CCTP }) @@ -789,11 +883,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { "action context encoded from BridgeActionContext" ); assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453"); - assertEq(result.actions[1].quarkAccount, address(0xa11ce), "0xa11ce sends the funds"); + assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds"); assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[1].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base"); assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 1e5"); + assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[1].totalPlays, 1, "total plays is 1"); assertEq( result.actions[1].actionContext, abi.encode( @@ -824,7 +920,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](3); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList_(1, address(0xa11ce), 8e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -832,7 +928,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(8453, address(0xb0b), 4e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -840,7 +936,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, - quarkStates: quarkStates_(address(0xfe11a), 2), + quarkSecrets: quarkSecrets_(address(0xfe11a), bytes32(uint256(2))), assetPositionsList: assetPositionsList_(7777, address(0xfe11a), 5e6), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -929,7 +1025,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -971,6 +1067,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "1 action"); @@ -979,6 +1077,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is USD"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1032,7 +1132,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -1082,6 +1182,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "1 action"); @@ -1090,6 +1192,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1143,7 +1247,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -1194,6 +1298,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "1 action"); @@ -1202,6 +1308,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[0].paymentMethod, "QUOTE_CALL", "payment method is 'QUOTE_CALL'"); assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( @@ -1255,7 +1363,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), bytes32(uint256(12))), assetPositionsList: assetPositionsList, cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -1297,6 +1405,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq( result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days" ); + assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce"); + assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false"); // check the actions assertEq(result.actions.length, 1, "1 action"); @@ -1305,6 +1415,8 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest { assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'"); assertEq(result.actions[0].paymentMethod, "OFFCHAIN", "payment method is 'OFFCHAIN'"); assertEq(result.actions[0].paymentToken, address(0), "payment token is USD"); + assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret"); + assertEq(result.actions[0].totalPlays, 1, "total plays is 1"); assertEq( result.actions[0].actionContext, abi.encode( diff --git a/test/builder/lib/QuarkBuilderTest.sol b/test/builder/lib/QuarkBuilderTest.sol index 863b6f72..7a3cc402 100644 --- a/test/builder/lib/QuarkBuilderTest.sol +++ b/test/builder/lib/QuarkBuilderTest.sol @@ -55,6 +55,10 @@ contract QuarkBuilderTest { address constant ETH_USD_PRICE_FEED_1 = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; address constant ETH_USD_PRICE_FEED_8453 = 0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70; + bytes32 constant ALICE_DEFAULT_SECRET = bytes32(uint256(12)); + bytes32 constant BOB_DEFAULT_SECRET = bytes32(uint256(2)); + bytes32 constant COB_DEFAULT_SECRET = bytes32(uint256(5)); + /** * * Fixture Functions @@ -92,7 +96,7 @@ contract QuarkBuilderTest { Accounts.ChainAccounts[] memory chainAccountsList = new Accounts.ChainAccounts[](3); chainAccountsList[0] = Accounts.ChainAccounts({ chainId: 1, - quarkStates: quarkStates_(address(0xa11ce), 12), + quarkSecrets: quarkSecrets_(address(0xa11ce), ALICE_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(1, address(0xa11ce), uint256(amount / 2)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -100,7 +104,7 @@ contract QuarkBuilderTest { }); chainAccountsList[1] = Accounts.ChainAccounts({ chainId: 8453, - quarkStates: quarkStates_(address(0xb0b), 2), + quarkSecrets: quarkSecrets_(address(0xb0b), BOB_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(8453, address(0xb0b), uint256(amount / 2)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -108,7 +112,7 @@ contract QuarkBuilderTest { }); chainAccountsList[2] = Accounts.ChainAccounts({ chainId: 7777, - quarkStates: quarkStates_(address(0xc0b), 5), + quarkSecrets: quarkSecrets_(address(0xc0b), COB_DEFAULT_SECRET), assetPositionsList: assetPositionsList_(7777, address(0xc0b), uint256(0)), cometPositions: emptyCometPositions_(), morphoPositions: emptyMorphoPositions_(), @@ -132,10 +136,10 @@ contract QuarkBuilderTest { return emptyMorphoVaultPositions; } - function quarkStates_() internal pure returns (Accounts.QuarkState[] memory) { - Accounts.QuarkState[] memory quarkStates = new Accounts.QuarkState[](1); - quarkStates[0] = quarkState_(); - return quarkStates; + function quarkSecrets_() internal pure returns (Accounts.QuarkSecret[] memory) { + Accounts.QuarkSecret[] memory quarkSecrets = new Accounts.QuarkSecret[](1); + quarkSecrets[0] = quarkSecret_(); + return quarkSecrets; } function maxCosts_(uint256 chainId, uint256 amount) internal pure returns (PaymentInfo.PaymentMaxCost[] memory) { @@ -285,24 +289,28 @@ contract QuarkBuilderTest { } } - function quarkStates_(address account, uint96 nextNonce) internal pure returns (Accounts.QuarkState[] memory) { - Accounts.QuarkState[] memory quarkStates = new Accounts.QuarkState[](1); - quarkStates[0] = quarkState_(account, nextNonce); - return quarkStates; + function quarkSecrets_(address account, bytes32 nonceSecret) + internal + pure + returns (Accounts.QuarkSecret[] memory) + { + Accounts.QuarkSecret[] memory quarkSecrets = new Accounts.QuarkSecret[](1); + quarkSecrets[0] = quarkSecret_(account, nonceSecret); + return quarkSecrets; } - function quarkState_() internal pure returns (Accounts.QuarkState memory) { - return quarkState_(address(0xa11ce), 3); + function quarkSecret_() internal pure returns (Accounts.QuarkSecret memory) { + return quarkSecret_(address(0xa11ce), bytes32(uint256(3))); } - function quarkState_(address account, uint96 nextNonce) internal pure returns (Accounts.QuarkState memory) { - return Accounts.QuarkState({account: account, quarkNextNonce: nextNonce}); + function quarkSecret_(address account, bytes32 nonceSecret) internal pure returns (Accounts.QuarkSecret memory) { + return Accounts.QuarkSecret({account: account, nonceSecret: nonceSecret}); } struct ChainPortfolio { uint256 chainId; address account; - uint96 nextNonce; + bytes32 nonceSecret; string[] assetSymbols; uint256[] assetBalances; CometPortfolio[] cometPortfolios; @@ -356,7 +364,7 @@ contract QuarkBuilderTest { for (uint256 i = 0; i < chainPortfolios.length; ++i) { chainAccountsList[i] = Accounts.ChainAccounts({ chainId: chainPortfolios[i].chainId, - quarkStates: quarkStates_(chainPortfolios[i].account, chainPortfolios[i].nextNonce), + quarkSecrets: quarkSecrets_(chainPortfolios[i].account, chainPortfolios[i].nonceSecret), assetPositionsList: assetPositionsForAssets( chainPortfolios[i].chainId, chainPortfolios[i].account, diff --git a/test/builder/lib/ReplayableHelper.sol b/test/builder/lib/ReplayableHelper.sol new file mode 100644 index 00000000..7fe07d71 --- /dev/null +++ b/test/builder/lib/ReplayableHelper.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.27; + +library ReplayableHelper { + function generateNonceFromSecret(bytes32 secret, uint256 totalPlays) internal pure returns (bytes32) { + uint256 replayCount = totalPlays - 1; + for (uint256 i = 0; i < replayCount; ++i) { + secret = keccak256(abi.encodePacked(secret)); + } + return secret; + } +}