Skip to content

Commit 9654393

Browse files
author
banky
committed
Slow and steady progress
1 parent a63065a commit 9654393

8 files changed

+259
-80
lines changed

src/builder/QuarkBuilderBase.sol

+116-36
Large diffs are not rendered by default.

src/builder/SimulationHelper.sol

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
pragma solidity ^0.8.27;
3+
4+
import {QuarkBuilderBase} from "src/builder/QuarkBuilderBase.sol";
5+
6+
library SimulationHelper {
7+
// Estimate from CCTP bridge action (including paycall): 0x894f0e4c944db179d0f573aab2c349d7f6df07690ac3772cf744e73a9208a79d
8+
uint256 constant BRIDGE_GAS_AMOUNT = 430_000;
9+
10+
function getMaxCost(
11+
uint256 chainId,
12+
QuarkBuilderBase.GasPricesResult memory gasPrices,
13+
uint256 estimatedGas
14+
) internal pure returns (uint256) {
15+
QuarkBuilderBase.GasPrice memory gasPrice = getGasPrice(
16+
chainId,
17+
gasPrices
18+
);
19+
20+
return
21+
(estimatedGas * gasPrice.ethGasPrice * gasPrices.currencyPrice) /
22+
1e18;
23+
}
24+
25+
function getGasPrice(
26+
uint256 chainId,
27+
QuarkBuilderBase.GasPricesResult memory gasPrices
28+
) internal pure returns (QuarkBuilderBase.GasPrice memory) {
29+
QuarkBuilderBase.GasPrice memory gasPrice;
30+
31+
for (uint256 i = 0; i < gasPrices.gasPrices.length; i++) {
32+
if (gasPrices.gasPrices[i].chainId == chainId) {
33+
gasPrice = gasPrices.gasPrices[i];
34+
break;
35+
}
36+
}
37+
38+
return gasPrice;
39+
}
40+
}

src/builder/actions/Actions.sol

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ library Actions {
7070

7171
/* FFI Addresses (starts from 0xFF1000, FFI with 100 reserved addresses) */
7272
address constant SIMULATION_FFI_ADDRESS = address(0xFF1001);
73+
address constant GAS_PRICE_FFI_ADDRESS = address(0xFF1002);
7374

7475
/* ===== Custom Errors ===== */
7576

src/builder/actions/TransferActionsBuilder.sol

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ contract TransferActionsBuilder is QuarkBuilderBase {
4242
// TransferMax will always use quotecall to avoid leaving dust in wallet
4343
bool useQuotecall = isMaxTransfer;
4444

45+
// TODO: The intent shouldn't be changed! in the builder function we need to determine how much
46+
// amount out if it is max...
4547
// Convert transferIntent to user aggregated balance
4648
if (isMaxTransfer) {
4749
transferIntent.amount = Accounts.totalAvailableAsset(transferIntent.assetSymbol, chainAccountsList, payment);

src/interfaces/IFFI.sol

+5
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,9 @@ interface IFFI {
1111
external
1212
pure
1313
returns (QuarkBuilderBase.Simulation[] memory);
14+
15+
function getGasPrices(string memory currencySymbol)
16+
external
17+
view
18+
returns (QuarkBuilderBase.GasPricesResult memory);
1419
}

test/builder/QuarkBuilderTransfer.t.sol

+54-42
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import "forge-std/console2.sol";
77

88
import {QuarkBuilderTest} from "test/builder/lib/QuarkBuilderTest.sol";
99
import {SimulationFFI} from "test/mocks/SimulationFFI.sol";
10+
import {GasPriceFFI} from "test/mocks/GasPriceFFI.sol";
1011

1112
import {CCTPBridgeActions} from "src/BridgeScripts.sol";
1213
import {Multicall} from "src/Multicall.sol";
@@ -91,11 +92,21 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
9192
});
9293
}
9394

94-
function setupSimulation() internal {
95+
function setUp() external {
96+
setupSimulationFFI();
97+
setupGasEstimateFFI();
98+
}
99+
100+
function setupSimulationFFI() internal {
95101
SimulationFFI mockFFI = new SimulationFFI();
96102
vm.etch(Actions.SIMULATION_FFI_ADDRESS, address(mockFFI).code);
97103
}
98104

105+
function setupGasEstimateFFI() internal {
106+
GasPriceFFI mockFFI = new GasPriceFFI();
107+
vm.etch(Actions.GAS_PRICE_FFI_ADDRESS, address(mockFFI).code);
108+
}
109+
99110
function testInsufficientFunds() public {
100111
QuarkBuilder builder = new QuarkBuilder();
101112
vm.expectRevert(abi.encodeWithSelector(QuarkBuilderBase.FundsUnavailable.selector, "USDC", 10e6, 0e6));
@@ -143,7 +154,6 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
143154

144155
function testSimpleLocalTransferSucceeds() public {
145156
QuarkBuilder builder = new QuarkBuilder();
146-
setupSimulation();
147157
QuarkBuilder.BuilderResult memory result = builder.transfer(
148158
transferUsdc_(1, 1e6, address(0xceecee), BLOCK_TIMESTAMP), // transfer 1 USDC on chain 1 to 0xceecee
149159
chainAccountsList_(3e6), // holding 3 USDC in total across chains 1, 8453
@@ -218,12 +228,11 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
218228

219229
function testSimpleLocalTransferWithPaycallSucceeds() public {
220230
QuarkBuilder builder = new QuarkBuilder();
221-
setupSimulation();
222231
PaymentInfo.PaymentMaxCost[] memory maxCosts = new PaymentInfo.PaymentMaxCost[](1);
223232
maxCosts[0] = PaymentInfo.PaymentMaxCost({chainId: 1, amount: 1e5});
224233
QuarkBuilder.BuilderResult memory result = builder.transfer(
225234
transferUsdc_(1, 1e6, address(0xceecee), BLOCK_TIMESTAMP), // transfer 1 usdc on chain 1 to 0xceecee
226-
chainAccountsList_(3e6), // holding 3USDC on chains 1, 8453
235+
chainAccountsList_(14e6), // holding 14USDC on chains 1, 8453
227236
paymentUsdc_(maxCosts)
228237
);
229238

@@ -245,7 +254,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
245254
Paycall.run.selector,
246255
transferActionsAddress,
247256
abi.encodeWithSelector(TransferActions.transferERC20Token.selector, usdc_(1), address(0xceecee), 1e6),
248-
1.1e6
257+
5562742 // Mock simulation cost
249258
),
250259
"calldata is Paycall.run(TransferActions.transferERC20Token(USDC_1, address(0xceecee), 1e6), 20e6);"
251260
);
@@ -262,7 +271,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
262271
assertEq(result.actions[0].actionType, "TRANSFER", "action type is 'TRANSFER'");
263272
assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'");
264273
assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC");
265-
assertEq(result.actions[0].paymentMaxCost, 1e5, "payment max is set to 1e5 in this test case");
274+
assertEq(result.actions[0].paymentMaxCost, 5562742, "payment max is set to 5562742 in this test case");
266275
assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret");
267276
assertEq(result.actions[0].totalPlays, 1, "total plays is 1");
268277
assertEq(
@@ -443,12 +452,12 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
443452
transferToken_({
444453
assetSymbol: "USDC",
445454
chainId: 8453,
446-
amount: 5e6,
455+
amount: 12e6,
447456
sender: address(0xb0b),
448457
recipient: address(0xceecee),
449458
blockTimestamp: BLOCK_TIMESTAMP
450459
}), // transfer 5 USDC on chain 8453 to 0xceecee
451-
chainAccountsList_(6e6), // holding 6 USDC in total across chains 1, 8453
460+
chainAccountsList_(20e6), // holding 6 USDC in total across chains 1, 8453
452461
paymentUsdc_(maxCosts)
453462
);
454463
address paycallAddress = paycallUsdc_(1);
@@ -472,12 +481,12 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
472481
abi.encodeWithSelector(
473482
CCTPBridgeActions.bridgeUSDC.selector,
474483
address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155),
475-
2.1e6,
484+
2006409,
476485
6,
477486
bytes32(uint256(uint160(0xb0b))),
478487
usdc_(1)
479488
),
480-
0.5e6
489+
5562742
481490
),
482491
"calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 2.1e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 5e5);"
483492
);
@@ -497,8 +506,10 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
497506
abi.encodeWithSelector(
498507
Paycall.run.selector,
499508
CodeJarHelper.getCodeAddress(type(TransferActions).creationCode),
500-
abi.encodeWithSelector(TransferActions.transferERC20Token.selector, usdc_(8453), address(0xceecee), 5e6),
501-
0.1e6
509+
abi.encodeWithSelector(
510+
TransferActions.transferERC20Token.selector, usdc_(8453), address(0xceecee), 12e6
511+
),
512+
6409
502513
),
503514
"calldata is Paycall.run(TransferActions.transferERC20Token(USDC_8453, address(0xceecee), 5e6), 1e5);"
504515
);
@@ -515,14 +526,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
515526
assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'");
516527
assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'");
517528
assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet");
518-
assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 5e5");
529+
assertEq(result.actions[0].paymentMaxCost, 5562742, "payment should have max cost of 5562742");
519530
assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret");
520531
assertEq(result.actions[0].totalPlays, 1, "total plays is 1");
521532
assertEq(
522533
result.actions[0].actionContext,
523534
abi.encode(
524535
Actions.BridgeActionContext({
525-
amount: 2.1e6,
536+
amount: 2006409,
526537
price: USDC_PRICE,
527538
token: USDC_1,
528539
assetSymbol: "USDC",
@@ -539,14 +550,14 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
539550
assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'");
540551
assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'");
541552
assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base");
542-
assertEq(result.actions[1].paymentMaxCost, 0.1e6, "payment should have max cost of 1e5");
553+
assertEq(result.actions[1].paymentMaxCost, 6409, "payment should have max cost of 6409");
543554
assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret");
544555
assertEq(result.actions[1].totalPlays, 1, "total plays is 1");
545556
assertEq(
546557
result.actions[1].actionContext,
547558
abi.encode(
548559
Actions.TransferActionContext({
549-
amount: 5e6,
560+
amount: 12e6,
550561
price: USDC_PRICE,
551562
token: USDC_8453,
552563
assetSymbol: "USDC",
@@ -573,13 +584,13 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
573584
QuarkBuilder.BuilderResult memory result = builder.transfer(
574585
transferToken_({
575586
assetSymbol: "USDT",
576-
chainId: 8453,
587+
chainId: 1,
577588
amount: 3e6,
578589
sender: address(0xb0b),
579590
recipient: address(0xceecee),
580591
blockTimestamp: BLOCK_TIMESTAMP
581592
}), // transfer 3 USDT on chain 8453 to 0xceecee
582-
chainAccountsList_(6e6), // holding 6 USDC and USDT in total across chains 1, 8453
593+
chainAccountsList_(9e6), // USDC and USDT in total across chains 1, 8453
583594
paymentUsdc_(maxCosts)
584595
);
585596
address paycallAddress = paycallUsdc_(1);
@@ -592,7 +603,7 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
592603
assertEq(result.quarkOperations.length, 2, "two operations");
593604
assertEq(
594605
result.quarkOperations[0].scriptAddress,
595-
paycallAddress,
606+
paycallAddressBase,
596607
"script address[0] has been wrapped with paycall address"
597608
);
598609
assertEq(
@@ -602,34 +613,34 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
602613
cctpBridgeActionsAddress,
603614
abi.encodeWithSelector(
604615
CCTPBridgeActions.bridgeUSDC.selector,
605-
address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155),
606-
1.5e6,
607-
6,
616+
address(0x1682Ae6375C4E4A97e4B583BC394c861A46D8962),
617+
1062742,
618+
0,
608619
bytes32(uint256(uint160(0xb0b))),
609-
usdc_(1)
620+
usdc_(8453)
610621
),
611-
0.5e6
622+
6409
612623
),
613624
"calldata is Paycall.run(CCTPBridgeActions.bridgeUSDC(address(0xBd3fa81B58Ba92a82136038B25aDec7066af3155), 1.5e6, 6, bytes32(uint256(uint160(0xb0b))), usdc_(1))), 0.5e6);"
614625
);
615626
assertEq(
616627
result.quarkOperations[0].expiry, BLOCK_TIMESTAMP + 7 days, "expiry is current blockTimestamp + 7 days"
617628
);
618-
assertEq(result.quarkOperations[0].nonce, ALICE_DEFAULT_SECRET, "unexpected nonce");
629+
assertEq(result.quarkOperations[0].nonce, BOB_DEFAULT_SECRET, "unexpected nonce");
619630
assertEq(result.quarkOperations[0].isReplayable, false, "isReplayable is false");
620631

621632
assertEq(
622633
result.quarkOperations[1].scriptAddress,
623-
paycallAddressBase,
634+
paycallAddress,
624635
"script address[1] has been wrapped with paycall address"
625636
);
626637
assertEq(
627638
result.quarkOperations[1].scriptCalldata,
628639
abi.encodeWithSelector(
629640
Paycall.run.selector,
630641
CodeJarHelper.getCodeAddress(type(TransferActions).creationCode),
631-
abi.encodeWithSelector(TransferActions.transferERC20Token.selector, usdt_(8453), address(0xceecee), 3e6),
632-
4.5e6
642+
abi.encodeWithSelector(TransferActions.transferERC20Token.selector, usdt_(1), address(0xceecee), 3e6),
643+
5562742
633644
),
634645
"calldata is Paycall.run(TransferActions.transferERC20Token(USDT_8453, address(0xceecee), 3e6), 4.5e6);"
635646
);
@@ -641,36 +652,37 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
641652

642653
// Check the actions
643654
assertEq(result.actions.length, 2, "one action");
644-
assertEq(result.actions[0].chainId, 1, "operation is on chainid 1");
645-
assertEq(result.actions[0].quarkAccount, address(0xa11ce), "0xa11ce sends the funds");
655+
assertEq(result.actions[0].chainId, 8453, "operation is on chainid 8453");
656+
// TODO: It's not clear to me how this was A11ce before, seems should be b0b?
657+
assertEq(result.actions[0].quarkAccount, address(0xb0b), "0xb0b sends the funds");
646658
assertEq(result.actions[0].actionType, "BRIDGE", "action type is 'BRIDGE'");
647659
assertEq(result.actions[0].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'");
648-
assertEq(result.actions[0].paymentToken, USDC_1, "payment token is USDC on mainnet");
649-
assertEq(result.actions[0].paymentMaxCost, 0.5e6, "payment should have max cost of 0.5e6");
650-
assertEq(result.actions[0].nonceSecret, ALICE_DEFAULT_SECRET, "unexpected nonce secret");
660+
assertEq(result.actions[0].paymentToken, USDC_8453, "payment token is USDC on base");
661+
assertEq(result.actions[0].paymentMaxCost, 6409, "payment should have max cost of 6409");
662+
assertEq(result.actions[0].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret");
651663
assertEq(result.actions[0].totalPlays, 1, "total plays is 1");
652664
assertEq(
653665
result.actions[0].actionContext,
654666
abi.encode(
655667
Actions.BridgeActionContext({
656-
amount: 1.5e6,
668+
amount: 1062742,
657669
price: USDC_PRICE,
658-
token: USDC_1,
670+
token: USDC_8453,
659671
assetSymbol: "USDC",
660-
chainId: 1,
672+
chainId: 8453,
661673
recipient: address(0xb0b),
662-
destinationChainId: 8453,
674+
destinationChainId: 1,
663675
bridgeType: Actions.BRIDGE_TYPE_CCTP
664676
})
665677
),
666678
"action context encoded from BridgeActionContext"
667679
);
668-
assertEq(result.actions[1].chainId, 8453, "operation is on chainid 8453");
680+
assertEq(result.actions[1].chainId, 1, "operation is on chainid 8453");
669681
assertEq(result.actions[1].quarkAccount, address(0xb0b), "0xb0b sends the funds");
670682
assertEq(result.actions[1].actionType, "TRANSFER", "action type is 'TRANSFER'");
671683
assertEq(result.actions[1].paymentMethod, "PAY_CALL", "payment method is 'PAY_CALL'");
672-
assertEq(result.actions[1].paymentToken, USDC_8453, "payment token is USDC on Base");
673-
assertEq(result.actions[1].paymentMaxCost, 4.5e6, "payment should have max cost of 4.5e6");
684+
assertEq(result.actions[1].paymentToken, USDC_1, "payment token is USDC on Mainnet");
685+
assertEq(result.actions[1].paymentMaxCost, 5562742, "payment should have max cost of 5562742");
674686
assertEq(result.actions[1].nonceSecret, BOB_DEFAULT_SECRET, "unexpected nonce secret");
675687
assertEq(result.actions[1].totalPlays, 1, "total plays is 1");
676688
assertEq(
@@ -679,9 +691,9 @@ contract QuarkBuilderTransferTest is Test, QuarkBuilderTest {
679691
Actions.TransferActionContext({
680692
amount: 3e6,
681693
price: USDT_PRICE,
682-
token: USDT_8453,
694+
token: USDT_1,
683695
assetSymbol: "USDT",
684-
chainId: 8453,
696+
chainId: 1,
685697
recipient: address(0xceecee)
686698
})
687699
),

test/mocks/GasPriceFFI.sol

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
pragma solidity 0.8.27;
3+
4+
import "../../src/builder/Strings.sol";
5+
import {QuarkBuilderBase} from "src/builder/QuarkBuilderBase.sol";
6+
7+
contract GasPriceFFI {
8+
// TODO: Update to pass in list of chainIds
9+
function getGasPrices(
10+
string memory currency
11+
) external pure returns (QuarkBuilderBase.GasPricesResult memory) {
12+
QuarkBuilderBase.GasPricesResult memory result;
13+
result.currency = currency;
14+
if (Strings.stringEq(currency, "usdc")) {
15+
result.currencyPrice = 2500 * 1e6;
16+
} else {
17+
revert("Unsupported currency");
18+
}
19+
20+
uint256[] memory chainIds = new uint256[](2);
21+
chainIds[0] = 1;
22+
chainIds[1] = 8453;
23+
24+
QuarkBuilderBase.GasPrice[]
25+
memory gasPrices = new QuarkBuilderBase.GasPrice[](chainIds.length);
26+
for (uint256 i = 0; i < chainIds.length; i++) {
27+
if (chainIds[i] == 1) {
28+
gasPrices[i].chainId = 1;
29+
gasPrices[i].ethGasPrice = 8607726355;
30+
} else {
31+
gasPrices[i].chainId = 8453;
32+
gasPrices[i].ethGasPrice = 9919085;
33+
}
34+
}
35+
result.gasPrices = gasPrices;
36+
37+
return result;
38+
}
39+
}

0 commit comments

Comments
 (0)