diff --git a/.env-example b/.env-example index f505587f..0974c015 100644 --- a/.env-example +++ b/.env-example @@ -4,7 +4,7 @@ BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/KEY GOERLI_RPC_URL=https://eth-goerli.g.alchemy.com/v2/KEY OPTIMISM_GOERLI_RPC_URL=https://opt-goerli.g.alchemy.com/v2/KEY BASE_GOERLI_RPC_URL=https://base-goerli.g.alchemy.com/v2/KEY -PRIVATE_KEY=KEY +PRIVATE_KEY=0xKEY ETHERSCAN_API_KEY=KEY OPTIMISM_ETHERSCAN_API_KEY=KEY BASESCAN_API_KEY=KEY \ No newline at end of file diff --git a/.gas-snapshot b/.gas-snapshot index 8bfe1c4c..ea49b88e 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,43 +1,45 @@ AccountDelegate:test_isAccountDelegate_account_doesnt_exist() (gas: 26514) -AccountDelegate:test_isAccountDelegate_false() (gas: 391991) -AccountDelegate:test_isAccountDelegate_true() (gas: 389960) -AccountOwner:test_isAccountOwner_account_doesnt_exist() (gas: 25032) -AccountOwner:test_isAccountOwner_false() (gas: 229812) -AccountOwner:test_isAccountOwner_true() (gas: 229802) -CanExecute:test_canExecute_false_nonce_used() (gas: 635740) -CanExecute:test_canExecute_false_trusted_executor() (gas: 44071) -CanExecute:test_canExecute_true() (gas: 43670) -CommitOrder:test_commitOrder() (gas: 382895) -CommitOrder:test_commitOrder_insufficient_collateral() (gas: 441115) -CommitOrder:test_commitOrder_invalid_market() (gas: 38252) +AccountDelegate:test_isAccountDelegate_false() (gas: 391946) +AccountDelegate:test_isAccountDelegate_true() (gas: 389915) +AccountOwner:test_isAccountOwner_account_doesnt_exist() (gas: 25010) +AccountOwner:test_isAccountOwner_false() (gas: 229790) +AccountOwner:test_isAccountOwner_true() (gas: 229780) +CanExecute:test_canExecute_false_nonce_used() (gas: 635986) +CanExecute:test_canExecute_false_trusted_executor() (gas: 44106) +CanExecute:test_canExecute_true() (gas: 43793) +CommitOrder:test_commitOrder() (gas: 382994) +CommitOrder:test_commitOrder_insufficient_collateral() (gas: 441147) +CommitOrder:test_commitOrder_invalid_market() (gas: 38351) Conditions:test_isMarketOpen() (gas: 26586) -Conditions:test_isOrderFeeBelow() (gas: 179759) -Conditions:test_isPositionSizeAbove() (gas: 18801) -Conditions:test_isPositionSizeBelow() (gas: 18783) -Conditions:test_isPriceAbove() (gas: 19194) -Conditions:test_isPriceBelow() (gas: 18998) -Conditions:test_isTimestampAfter() (gas: 7645) -Conditions:test_isTimestampBefore() (gas: 7668) -DeploymentTest:test_deploy() (gas: 2684850) -DeploymentTest:test_deploy_oracle_zero_address() (gas: 39879) -DeploymentTest:test_deploy_perps_market_proxy_zero_address() (gas: 39775) -DeploymentTest:test_deploy_spot_market_proxy_zero_address() (gas: 39853) -DeploymentTest:test_deploy_susd_proxy_zero_address() (gas: 39866) -DepositCollateral:test_depositCollateral() (gas: 258373) -DepositCollateral:test_depositCollateral_availableMargin() (gas: 265941) -DepositCollateral:test_depositCollateral_collateralAmount() (gas: 258949) -DepositCollateral:test_depositCollateral_insufficient_balance() (gas: 55891) -DepositCollateral:test_depositCollateral_totalCollateralValue() (gas: 263325) -Execute:test_execute_CannotExecuteOrder() (gas: 37262) -Execute:test_execute_leverage_exceeded() (gas: 716169) -Execute:test_execute_order_committed() (gas: 633720) -Fee:test_fee_imposed() (gas: 639801) -Fee:test_fee_imposed_above_upper_fee_cap() (gas: 618512) -Fee:test_fee_imposed_at_upper_fee_cap() (gas: 618415) -Fee:test_fee_imposed_below_lower_fee_cap() (gas: 617062) -Fee:test_fee_imposed_below_upper_fee_cap() (gas: 619335) -Fee:test_fee_imposed_fee_cannot_be_paid() (gas: 393807) -Fee:test_fee_imposed_insufficient_collateral_for_order() (gas: 716154) +Conditions:test_isOrderFeeBelow() (gas: 179847) +Conditions:test_isPositionSizeAbove() (gas: 18867) +Conditions:test_isPositionSizeBelow() (gas: 18762) +Conditions:test_isPriceAbove() (gas: 19150) +Conditions:test_isPriceBelow() (gas: 19178) +Conditions:test_isTimestampAfter() (gas: 7513) +Conditions:test_isTimestampBefore() (gas: 7602) +DeploymentTest:test_deploy() (gas: 2880751) +DeploymentTest:test_deploy_oracle_zero_address() (gas: 40293) +DeploymentTest:test_deploy_perps_market_proxy_zero_address() (gas: 40211) +DeploymentTest:test_deploy_spot_market_proxy_zero_address() (gas: 40267) +DeploymentTest:test_deploy_susd_proxy_zero_address() (gas: 40280) +DeploymentTest:test_deploy_trusted_forwarder_zero_address() (gas: 40327) +DepositCollateral:test_depositCollateral() (gas: 258507) +DepositCollateral:test_depositCollateral_availableMargin() (gas: 266075) +DepositCollateral:test_depositCollateral_collateralAmount() (gas: 259083) +DepositCollateral:test_depositCollateral_insufficient_balance() (gas: 56023) +DepositCollateral:test_depositCollateral_totalCollateralValue() (gas: 263459) +EIP7412Test:test_fulfillOracleQuery(bytes) (runs: 256, μ: 142463, ~: 142365) +Execute:test_execute_CannotExecuteOrder() (gas: 37220) +Execute:test_execute_leverage_exceeded() (gas: 716391) +Execute:test_execute_order_committed() (gas: 633942) +Fee:test_fee_imposed() (gas: 640084) +Fee:test_fee_imposed_above_upper_fee_cap() (gas: 618750) +Fee:test_fee_imposed_at_upper_fee_cap() (gas: 618653) +Fee:test_fee_imposed_below_lower_fee_cap() (gas: 617168) +Fee:test_fee_imposed_below_upper_fee_cap() (gas: 619573) +Fee:test_fee_imposed_fee_cannot_be_paid() (gas: 394180) +Fee:test_fee_imposed_insufficient_collateral_for_order() (gas: 716414) MathLibTest:test_abs128() (gas: 448) MathLibTest:test_abs256() (gas: 480) MathLibTest:test_castU128() (gas: 350) @@ -45,27 +47,27 @@ MathLibTest:test_castU128_overflow() (gas: 3509) MathLibTest:test_fuzz_abs128(int128) (runs: 256, μ: 576, ~: 603) MathLibTest:test_fuzz_abs256(int256) (runs: 256, μ: 472, ~: 458) MathLibTest:test_isSameSign() (gas: 999) -MulticallableTest:test_multicall_depositCollateral_commitOrder() (gas: 604961) -NonceBitmapTest:test_fuzz_invalidateUnorderedNonces(uint256) (runs: 256, μ: 52614, ~: 52614) -NonceBitmapTest:test_hasUnorderedNonceBeenUsed() (gas: 53925) -NonceBitmapTest:test_invalidateUnorderedNonces() (gas: 72854) -NonceBitmapTest:test_invalidateUnorderedNonces_Only_Owner_Delegate() (gas: 190012) -ReduceOnly:test_reduce_only() (gas: 635354) -ReduceOnly:test_reduce_only_same_sign() (gas: 68419) -ReduceOnly:test_reduce_only_truncate_size_down() (gas: 635484) -ReduceOnly:test_reduce_only_truncate_size_up() (gas: 610287) -ReduceOnly:test_reduce_only_zero_size() (gas: 158892) -VerifyConditions:test_max_condition_size_exceeded() (gas: 45046) -VerifyConditions:test_verifyConditions_InvalidConditionSelector() (gas: 14079) -VerifyConditions:test_verify_conditions_not_verified() (gas: 29659) -VerifyConditions:test_verify_conditions_verified() (gas: 135808) -VerifySignature:test_verifySignature() (gas: 23770) -VerifySignature:test_verifySignature_false_private_key() (gas: 26592) -VerifySigner:test_verifySigner() (gas: 25823) -VerifySigner:test_verifySigner_false() (gas: 28531) -WithdrawCollateral:test_withdrawCollateral() (gas: 352984) -WithdrawCollateral:test_withdrawCollateral_availableMargin() (gas: 354503) -WithdrawCollateral:test_withdrawCollateral_collateralAmount() (gas: 353495) -WithdrawCollateral:test_withdrawCollateral_insufficient_account_collateral_balance() (gas: 273929) -WithdrawCollateral:test_withdrawCollateral_totalCollateralValue() (gas: 354001) -WithdrawCollateral:test_withdrawCollateral_zero() (gas: 265887) \ No newline at end of file +MulticallableTest:test_multicall_depositCollateral_commitOrder() (gas: 605194) +NonceBitmapTest:test_fuzz_invalidateUnorderedNonces(uint256) (runs: 256, μ: 52780, ~: 52780) +NonceBitmapTest:test_hasUnorderedNonceBeenUsed() (gas: 54136) +NonceBitmapTest:test_invalidateUnorderedNonces() (gas: 73100) +NonceBitmapTest:test_invalidateUnorderedNonces_Only_Owner_Delegate() (gas: 190344) +ReduceOnly:test_reduce_only() (gas: 635576) +ReduceOnly:test_reduce_only_same_sign() (gas: 68498) +ReduceOnly:test_reduce_only_truncate_size_down() (gas: 635706) +ReduceOnly:test_reduce_only_truncate_size_up() (gas: 610509) +ReduceOnly:test_reduce_only_zero_size() (gas: 159015) +VerifyConditions:test_max_condition_size_exceeded() (gas: 45024) +VerifyConditions:test_verifyConditions_InvalidConditionSelector() (gas: 14123) +VerifyConditions:test_verify_conditions_not_verified() (gas: 29682) +VerifyConditions:test_verify_conditions_verified() (gas: 135831) +VerifySignature:test_verifySignature() (gas: 23816) +VerifySignature:test_verifySignature_false_private_key() (gas: 26638) +VerifySigner:test_verifySigner() (gas: 25845) +VerifySigner:test_verifySigner_false() (gas: 28553) +WithdrawCollateral:test_withdrawCollateral() (gas: 353200) +WithdrawCollateral:test_withdrawCollateral_availableMargin() (gas: 354718) +WithdrawCollateral:test_withdrawCollateral_collateralAmount() (gas: 353710) +WithdrawCollateral:test_withdrawCollateral_insufficient_account_collateral_balance() (gas: 274196) +WithdrawCollateral:test_withdrawCollateral_totalCollateralValue() (gas: 354216) +WithdrawCollateral:test_withdrawCollateral_zero() (gas: 266154) \ No newline at end of file diff --git a/README.md b/README.md index b7da1ac3..57b25da1 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ src/ │ ├── oracles │ │ └── IPyth.sol │ ├── synthetix +│ │ ├── IERC7412.sol │ │ ├── IPerpsMarketProxy.sol │ │ └── ISpotMarketProxy.sol │ └── tokens @@ -37,6 +38,8 @@ src/ │ └── SignatureCheckerLib.sol └── utils ├── EIP712.sol + ├── EIP7412.sol + ├── ERC2771Context.sol └── Multicallable.sol ``` diff --git a/deployments/BaseGoerli.json b/deployments/BaseGoerli.json index 769394f3..153c83cc 100644 --- a/deployments/BaseGoerli.json +++ b/deployments/BaseGoerli.json @@ -1,3 +1,3 @@ { - "Engine": "0xb3E58002aAf9d21a39a19DB784f10c30c9e5bE76" + "Engine": "0x319Ae7F3a0D635eD9CCF0276dCeAF680F9C7c397" } \ No newline at end of file diff --git a/deployments/OptimismGoerli.json b/deployments/OptimismGoerli.json index 864d440f..85ca6b3e 100644 --- a/deployments/OptimismGoerli.json +++ b/deployments/OptimismGoerli.json @@ -1,3 +1,3 @@ { - "Engine": "0xB52640E47b3fe5bdcB113430328BF5ea96261160" + "Engine": "0x16Da5d51C1DDB43C1105A253F2dE9eAB2B3b76CA" } \ No newline at end of file diff --git a/diagrams/SMv3-Overview.png b/diagrams/SMv3-Overview.png index 38785a12..80813ff8 100644 Binary files a/diagrams/SMv3-Overview.png and b/diagrams/SMv3-Overview.png differ diff --git a/lcov.info b/lcov.info index e14e1bd4..73ee62ae 100644 --- a/lcov.info +++ b/lcov.info @@ -1,32 +1,32 @@ TN: SF:script/Deploy.s.sol -FN:17,Setup.deploySystem -FNDA:0,Setup.deploySystem -DA:23,0 -FN:36,DeployBase.run -FNDA:0,DeployBase.run -DA:37,0 -DA:38,0 -DA:40,0 -DA:47,0 -FN:94,DeployOptimismGoerli.run -FNDA:0,DeployOptimismGoerli.run -DA:95,0 -DA:96,0 -DA:98,0 -DA:105,0 -FN:55,DeployBaseGoerli.run +FN:58,DeployBaseGoerli.run FNDA:0,DeployBaseGoerli.run -DA:56,0 -DA:57,0 DA:59,0 -DA:66,0 -FN:74,DeployOptimism.run +DA:60,0 +DA:62,0 +DA:70,0 +FN:38,DeployBase.run +FNDA:0,DeployBase.run +DA:39,0 +DA:40,0 +DA:42,0 +DA:50,0 +FN:78,DeployOptimism.run FNDA:0,DeployOptimism.run -DA:75,0 -DA:76,0 -DA:78,0 -DA:85,0 +DA:79,0 +DA:80,0 +DA:82,0 +DA:90,0 +FN:17,Setup.deploySystem +FNDA:0,Setup.deploySystem +DA:24,0 +FN:98,DeployOptimismGoerli.run +FNDA:0,DeployOptimismGoerli.run +DA:99,0 +DA:100,0 +DA:102,0 +DA:110,0 FNF:5 FNH:0 LF:17 @@ -36,199 +36,200 @@ BRH:0 end_of_record TN: SF:src/Engine.sol -FN:129,Engine.isAccountOwner +FN:134,Engine.isAccountOwner FNDA:3,Engine.isAccountOwner -DA:135,10 -FN:139,Engine.isAccountDelegate +DA:140,10 +FN:144,Engine.isAccountDelegate FNDA:2,Engine.isAccountDelegate -DA:145,2 -FN:150,Engine._isAccountOwnerOrDelegate +DA:150,2 +FN:155,Engine._isAccountOwnerOrDelegate FNDA:285,Engine._isAccountOwnerOrDelegate -DA:155,285 -FN:165,Engine.invalidateUnorderedNonces +DA:160,285 +FN:170,Engine.invalidateUnorderedNonces FNDA:260,Engine.invalidateUnorderedNonces -DA:170,260 -BRDA:170,0,0,260 -BRDA:170,0,1,- -DA:176,260 -DA:178,260 -DA:180,0 -FN:185,Engine.hasUnorderedNonceBeenUsed +DA:175,260 +BRDA:175,0,0,260 +BRDA:175,0,1,- +DA:181,260 +DA:183,260 +DA:185,0 +FN:190,Engine.hasUnorderedNonceBeenUsed FNDA:260,Engine.hasUnorderedNonceBeenUsed -DA:191,281 -DA:195,281 -DA:209,281 -FN:219,Engine._bitmapPositions +DA:196,281 +DA:200,281 +DA:214,281 +FN:224,Engine._bitmapPositions FNDA:296,Engine._bitmapPositions -DA:226,296 -DA:230,296 -FN:236,Engine._useUnorderedNonce +DA:231,296 +DA:235,296 +FN:241,Engine._useUnorderedNonce FNDA:15,Engine._useUnorderedNonce -DA:237,15 -DA:241,15 -DA:248,15 -DA:268,15 -BRDA:268,1,0,- -BRDA:268,1,1,15 -FN:276,Engine.modifyCollateral +DA:242,15 +DA:246,15 +DA:253,15 +DA:273,15 +BRDA:273,1,0,- +BRDA:273,1,1,15 +FN:281,Engine.modifyCollateral FNDA:19,Engine.modifyCollateral -DA:281,19 -DA:283,19 -BRDA:283,2,0,12 -BRDA:283,2,1,7 -DA:284,12 -DA:288,7 -BRDA:288,3,0,- -BRDA:288,3,1,7 -DA:289,7 -FN:295,Engine._depositCollateral +DA:286,19 +DA:288,19 +DA:290,19 +BRDA:290,2,0,12 +BRDA:290,2,1,7 +DA:291,12 +DA:295,7 +BRDA:295,3,0,- +BRDA:295,3,1,7 +DA:296,7 +FN:302,Engine._depositCollateral FNDA:12,Engine._depositCollateral -DA:303,12 -DA:305,11 -DA:307,11 -FN:310,Engine._withdrawCollateral +DA:310,12 +DA:312,11 +DA:314,11 +FN:317,Engine._withdrawCollateral FNDA:20,Engine._withdrawCollateral -DA:317,20 -DA:320,17 -FN:326,Engine._getSynthAddress +DA:324,20 +DA:327,17 +FN:333,Engine._getSynthAddress FNDA:19,Engine._getSynthAddress -DA:331,19 -FN:341,Engine.commitOrder +DA:338,19 +FN:348,Engine.commitOrder FNDA:4,Engine.commitOrder -DA:355,4 -BRDA:355,4,0,4 -BRDA:355,4,1,- -DA:356,4 -DA:366,0 -FN:370,Engine._commitOrder +DA:362,4 +BRDA:362,4,0,4 +BRDA:362,4,1,- +DA:363,4 +DA:373,0 +FN:377,Engine._commitOrder FNDA:16,Engine._commitOrder -DA:379,16 -FN:397,Engine.execute +DA:386,16 +FN:404,Engine.execute FNDA:16,Engine.execute -DA:410,16 -BRDA:410,5,0,1 -BRDA:410,5,1,15 -DA:413,15 -DA:417,15 +DA:417,16 +BRDA:417,5,0,1 +BRDA:417,5,1,15 DA:420,15 -BRDA:420,6,0,2 -BRDA:420,6,1,3 -DA:421,5 -DA:426,5 -BRDA:426,7,0,1 -BRDA:426,7,1,4 -DA:427,1 -DA:431,4 -BRDA:431,8,0,1 -BRDA:431,8,1,3 -DA:432,1 -DA:438,3 -BRDA:438,9,0,2 -BRDA:438,9,1,3 -DA:448,2 -DA:454,13 -DA:460,13 -DA:463,13 -BRDA:463,10,0,6 -BRDA:463,10,1,7 -DA:464,6 -DA:465,7 -BRDA:465,11,0,2 -BRDA:465,11,1,7 -DA:466,2 +DA:424,15 +DA:427,15 +BRDA:427,6,0,2 +BRDA:427,6,1,3 +DA:428,5 +DA:433,5 +BRDA:433,7,0,1 +BRDA:433,7,1,4 +DA:434,1 +DA:438,4 +BRDA:438,8,0,1 +BRDA:438,8,1,3 +DA:439,1 +DA:445,3 +BRDA:445,9,0,2 +BRDA:445,9,1,3 +DA:455,2 +DA:461,13 +DA:467,13 DA:470,13 -DA:479,12 -FN:491,Engine.canExecute +BRDA:470,10,0,6 +BRDA:470,10,1,7 +DA:471,6 +DA:472,7 +BRDA:472,11,0,2 +BRDA:472,11,1,7 +DA:473,2 +DA:477,13 +DA:486,12 +FN:498,Engine.canExecute FNDA:5,Engine.canExecute -DA:496,21 -BRDA:496,12,0,2 -BRDA:496,12,1,19 -DA:497,2 -DA:501,19 -BRDA:501,13,0,1 -BRDA:501,13,1,18 -DA:504,18 -BRDA:504,14,0,- -BRDA:504,14,1,18 -DA:507,18 -BRDA:507,15,0,- -BRDA:507,15,1,- -DA:510,0 -BRDA:510,16,0,- -BRDA:510,16,1,- +DA:503,21 +BRDA:503,12,0,2 +BRDA:503,12,1,19 +DA:504,2 +DA:508,19 +BRDA:508,13,0,1 +BRDA:508,13,1,18 +DA:511,18 +BRDA:511,14,0,- +BRDA:511,14,1,18 DA:514,18 -BRDA:514,17,0,1 -BRDA:514,17,1,17 -DA:517,17 -FN:525,Engine.verifySigner +BRDA:514,15,0,- +BRDA:514,15,1,- +DA:517,0 +BRDA:517,16,0,- +BRDA:517,16,1,- +DA:521,18 +BRDA:521,17,0,1 +BRDA:521,17,1,17 +DA:524,17 +FN:532,Engine.verifySigner FNDA:2,Engine.verifySigner -DA:531,21 -FN:535,Engine.verifySignature +DA:538,21 +FN:542,Engine.verifySignature FNDA:2,Engine.verifySignature -DA:539,20 -FN:545,Engine.verifyConditions +DA:546,20 +FN:552,Engine.verifyConditions FNDA:4,Engine.verifyConditions -DA:551,4 -DA:552,4 -BRDA:552,18,0,1 -BRDA:552,18,1,3 -DA:553,1 -DA:556,3 -DA:557,13 -DA:558,13 -DA:561,13 -DA:566,13 -DA:567,11 -DA:568,9 -DA:569,7 -DA:570,5 -DA:571,4 -DA:572,3 -DA:573,2 -BRDA:565,19,0,1 -BRDA:565,19,1,11 -DA:576,12 -DA:579,12 -BRDA:579,20,0,1 -BRDA:579,20,1,11 -DA:582,11 -DA:585,1 -DA:589,1 -FN:597,Engine.isTimestampAfter +DA:558,4 +DA:559,4 +BRDA:559,18,0,1 +BRDA:559,18,1,3 +DA:560,1 +DA:563,3 +DA:564,13 +DA:565,13 +DA:568,13 +DA:573,13 +DA:574,11 +DA:575,9 +DA:576,7 +DA:577,5 +DA:578,4 +DA:579,3 +DA:580,2 +BRDA:572,19,0,1 +BRDA:572,19,1,11 +DA:583,12 +DA:586,12 +BRDA:586,20,0,1 +BRDA:586,20,1,11 +DA:589,11 +DA:592,1 +DA:596,1 +FN:604,Engine.isTimestampAfter FNDA:5,Engine.isTimestampAfter -DA:603,5 -FN:607,Engine.isTimestampBefore +DA:610,5 +FN:614,Engine.isTimestampBefore FNDA:5,Engine.isTimestampBefore -DA:613,5 -FN:617,Engine.isPriceAbove +DA:620,5 +FN:624,Engine.isPriceAbove FNDA:6,Engine.isPriceAbove -DA:622,6 -DA:627,6 -FN:631,Engine.isPriceBelow +DA:629,6 +DA:634,6 +FN:638,Engine.isPriceBelow FNDA:6,Engine.isPriceBelow -DA:636,6 -DA:641,6 -FN:645,Engine.isMarketOpen +DA:643,6 +DA:648,6 +FN:652,Engine.isMarketOpen FNDA:3,Engine.isMarketOpen -DA:651,3 -FN:655,Engine.isPositionSizeAbove +DA:658,3 +FN:662,Engine.isPositionSizeAbove FNDA:4,Engine.isPositionSizeAbove -DA:660,4 -DA:661,4 -DA:663,4 -FN:667,Engine.isPositionSizeBelow +DA:667,4 +DA:668,4 +DA:670,4 +FN:674,Engine.isPositionSizeBelow FNDA:4,Engine.isPositionSizeBelow -DA:672,4 -DA:673,4 -DA:675,4 -FN:679,Engine.isOrderFeeBelow +DA:679,4 +DA:680,4 +DA:682,4 +FN:686,Engine.isOrderFeeBelow FNDA:4,Engine.isOrderFeeBelow -DA:685,4 -DA:690,4 +DA:692,4 +DA:697,4 FNF:26 FNH:26 -LF:95 -LH:92 +LF:96 +LH:93 BRF:42 BRH:33 end_of_record @@ -334,6 +335,53 @@ BRF:4 BRH:2 end_of_record TN: +SF:src/utils/EIP7412.sol +FN:14,EIP7412.fulfillOracleQuery +FNDA:256,EIP7412.fulfillOracleQuery +DA:18,256 +FNF:1 +FNH:1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/utils/ERC2771Context.sol +FN:52,ERC2771Context.trustedForwarder +FNDA:0,ERC2771Context.trustedForwarder +DA:53,314 +FN:59,ERC2771Context.isTrustedForwarder +FNDA:0,ERC2771Context.isTrustedForwarder +DA:60,314 +FN:68,ERC2771Context._msgSender +FNDA:314,ERC2771Context._msgSender +DA:69,314 +BRDA:69,0,0,- +BRDA:69,0,1,314 +DA:73,0 +DA:76,314 +FN:85,ERC2771Context._msgData +FNDA:0,ERC2771Context._msgData +DA:86,0 +BRDA:86,1,0,- +BRDA:86,1,1,- +DA:87,0 +DA:89,0 +FN:16,Context._msgSender +FNDA:314,Context._msgSender +DA:17,314 +FN:20,Context._msgData +FNDA:0,Context._msgData +DA:21,0 +FNF:6 +FNH:2 +LF:10 +LH:5 +BRF:4 +BRH:1 +end_of_record +TN: SF:src/utils/Multicallable.sol FN:19,Multicallable.multicall FNDA:1,Multicallable.multicall @@ -416,16 +464,16 @@ BRH:0 end_of_record TN: SF:test/utils/Bootstrap.sol -FN:132,BootstrapOptimismGoerli.init +FN:134,BootstrapOptimismGoerli.init FNDA:0,BootstrapOptimismGoerli.init -DA:136,0 -DA:143,0 -DA:150,0 +DA:138,0 +DA:146,0 +DA:154,0 FN:102,BootstrapOptimism.init FNDA:0,BootstrapOptimism.init DA:106,0 -DA:113,0 -DA:120,0 +DA:114,0 +DA:122,0 FN:36,Bootstrap.initializeOptimismGoerli FNDA:0,Bootstrap.initializeOptimismGoerli DA:37,0 @@ -567,21 +615,21 @@ BRH:0 end_of_record TN: SF:test/utils/exposed/EngineExposed.sol -FN:16,EngineExposed.getSynthAddress +FN:25,EngineExposed.getSynthAddress FNDA:0,EngineExposed.getSynthAddress -DA:21,0 -FN:24,EngineExposed.expose_UPPER_FEE_CAP +DA:30,0 +FN:33,EngineExposed.expose_UPPER_FEE_CAP FNDA:9,EngineExposed.expose_UPPER_FEE_CAP -DA:25,9 -FN:28,EngineExposed.expose_LOWER_FEE_CAP +DA:34,9 +FN:37,EngineExposed.expose_LOWER_FEE_CAP FNDA:2,EngineExposed.expose_LOWER_FEE_CAP -DA:29,2 -FN:32,EngineExposed.expose_FEE_SCALING_FACTOR +DA:38,2 +FN:41,EngineExposed.expose_FEE_SCALING_FACTOR FNDA:7,EngineExposed.expose_FEE_SCALING_FACTOR -DA:33,7 -FN:36,EngineExposed.expose_MAX_BPS +DA:42,7 +FN:45,EngineExposed.expose_MAX_BPS FNDA:0,EngineExposed.expose_MAX_BPS -DA:37,0 +DA:46,0 FNF:5 FNH:3 LF:5 @@ -603,18 +651,21 @@ BRH:0 end_of_record TN: SF:test/utils/mocks/SynthetixMock.sol -FN:8,SynthetixMock.mock_computeOrderFees +FN:9,SynthetixMock.mock_computeOrderFees FNDA:0,SynthetixMock.mock_computeOrderFees -DA:15,0 -FN:24,SynthetixMock.mock_getOpenPosition +DA:16,0 +FN:25,SynthetixMock.mock_getOpenPosition FNDA:0,SynthetixMock.mock_getOpenPosition -DA:30,0 -FN:39,SynthetixMock.mock_getMaxMarketSize +DA:31,0 +FN:40,SynthetixMock.mock_getMaxMarketSize FNDA:0,SynthetixMock.mock_getMaxMarketSize -DA:44,0 -FNF:3 +DA:45,0 +FN:54,SynthetixMock.mock_fulfillOracleQuery +FNDA:0,SynthetixMock.mock_fulfillOracleQuery +DA:58,0 +FNF:4 FNH:0 -LF:3 +LF:4 LH:0 BRF:0 BRH:0 diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 0874dda5..da1279bf 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -18,13 +18,15 @@ contract Setup is Script { address perpsMarketProxy, address spotMarketProxy, address sUSDProxy, - address oracle + address oracle, + address trustedForwarder ) public returns (Engine engine) { engine = new Engine({ _perpsMarketProxy: perpsMarketProxy, _spotMarketProxy: spotMarketProxy, _sUSDProxy: sUSDProxy, - _oracle: oracle + _oracle: oracle, + _trustedForwarder: trustedForwarder }); } } @@ -41,7 +43,8 @@ contract DeployBase is Setup, BaseParameters { perpsMarketProxy: PERPS_MARKET_PROXY, spotMarketProxy: SPOT_MARKET_PROXY, sUSDProxy: USD_PROXY, - oracle: PYTH + oracle: PYTH, + trustedForwarder: TRUSTED_FORWARDER }); vm.stopBroadcast(); @@ -60,7 +63,8 @@ contract DeployBaseGoerli is Setup, BaseGoerliParameters { perpsMarketProxy: PERPS_MARKET_PROXY, spotMarketProxy: SPOT_MARKET_PROXY, sUSDProxy: USD_PROXY, - oracle: PYTH + oracle: PYTH, + trustedForwarder: TRUSTED_FORWARDER }); vm.stopBroadcast(); @@ -79,7 +83,8 @@ contract DeployOptimism is Setup, OptimismParameters { perpsMarketProxy: PERPS_MARKET_PROXY, spotMarketProxy: SPOT_MARKET_PROXY, sUSDProxy: USD_PROXY, - oracle: PYTH + oracle: PYTH, + trustedForwarder: TRUSTED_FORWARDER }); vm.stopBroadcast(); @@ -89,7 +94,6 @@ contract DeployOptimism is Setup, OptimismParameters { /// @dev steps to deploy and verify on Optimism Goerli: /// (1) load the variables in the .env file via `source .env` /// (2) run `forge script script/Deploy.s.sol:DeployOptimismGoerli --rpc-url $OPTIMISM_GOERLI_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv` - contract DeployOptimismGoerli is Setup, OptimismGoerliParameters { function run() public { uint256 privateKey = vm.envUint("PRIVATE_KEY"); @@ -99,7 +103,8 @@ contract DeployOptimismGoerli is Setup, OptimismGoerliParameters { perpsMarketProxy: PERPS_MARKET_PROXY, spotMarketProxy: SPOT_MARKET_PROXY, sUSDProxy: USD_PROXY, - oracle: PYTH + oracle: PYTH, + trustedForwarder: TRUSTED_FORWARDER }); vm.stopBroadcast(); diff --git a/script/utils/parameters/BaseGoerliParameters.sol b/script/utils/parameters/BaseGoerliParameters.sol index b367e3a4..72f453de 100644 --- a/script/utils/parameters/BaseGoerliParameters.sol +++ b/script/utils/parameters/BaseGoerliParameters.sol @@ -12,4 +12,7 @@ contract BaseGoerliParameters { 0x579c612E4Bf390f5504DB9f76b6F5759A3172279; address public constant PYTH = 0x5955C1478F0dAD753C7E2B4dD1b4bC530C64749f; + + address public constant TRUSTED_FORWARDER = + 0xAE788aaf52780741E12BF79Ad684B91Bb0EF4D92; } diff --git a/script/utils/parameters/BaseParameters.sol b/script/utils/parameters/BaseParameters.sol index 01ea1811..19fb2717 100644 --- a/script/utils/parameters/BaseParameters.sol +++ b/script/utils/parameters/BaseParameters.sol @@ -9,4 +9,7 @@ contract BaseParameters { address public constant USD_PROXY = address(0); address public constant PYTH = 0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a; + + /// @custom:todo Base doesn't have a trusted forwarder yet + address public constant TRUSTED_FORWARDER = address(0); } diff --git a/script/utils/parameters/OptimismGoerliParameters.sol b/script/utils/parameters/OptimismGoerliParameters.sol index d22dd024..d299dc7c 100644 --- a/script/utils/parameters/OptimismGoerliParameters.sol +++ b/script/utils/parameters/OptimismGoerliParameters.sol @@ -12,4 +12,8 @@ contract OptimismGoerliParameters { 0xe487Ad4291019b33e2230F8E2FB1fb6490325260; address public constant PYTH = 0xff1a0f4744e8582DF1aE09D5611b887B6a12925C; + + /// @custom:todo Optimism Goerli doesn't have a trusted forwarder yet + address public constant TRUSTED_FORWARDER = + 0xAE788aaf52780741E12BF79Ad684B91Bb0EF4D92; } diff --git a/script/utils/parameters/OptimismParameters.sol b/script/utils/parameters/OptimismParameters.sol index d6e80c34..10679c61 100644 --- a/script/utils/parameters/OptimismParameters.sol +++ b/script/utils/parameters/OptimismParameters.sol @@ -11,4 +11,7 @@ contract OptimismParameters { 0xb2F30A7C980f052f02563fb518dcc39e6bf38175; address public constant PYTH = 0xff1a0f4744e8582DF1aE09D5611b887B6a12925C; + + /// @custom:todo Optimism doesn't have a trusted forwarder yet + address public constant TRUSTED_FORWARDER = address(0); } diff --git a/src/Engine.sol b/src/Engine.sol index cecd9981..aaf5d72e 100644 --- a/src/Engine.sol +++ b/src/Engine.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.18; import {ConditionalOrderHashLib} from "src/libraries/ConditionalOrderHashLib.sol"; import {EIP712} from "src/utils/EIP712.sol"; +import {EIP7412} from "src/utils/EIP7412.sol"; +import {ERC2771Context} from "src/utils/ERC2771Context.sol"; import {IEngine, IPerpsMarketProxy} from "src/interfaces/IEngine.sol"; import {IERC20} from "src/interfaces/tokens/IERC20.sol"; import {IPyth, PythStructs} from "src/interfaces/oracles/IPyth.sol"; @@ -15,7 +17,7 @@ import {SignatureCheckerLib} from "src/libraries/SignatureCheckerLib.sol"; /// @title Kwenta Smart Margin v3: Engine contract /// @notice Responsible for interacting with Synthetix v3 perps markets /// @author JaredBorders (jaredborders@pm.me) -contract Engine is IEngine, Multicallable, EIP712 { +contract Engine is IEngine, Multicallable, EIP712, EIP7412, ERC2771Context { using MathLib for int128; using MathLib for int256; using MathLib for uint256; @@ -104,16 +106,19 @@ contract Engine is IEngine, Multicallable, EIP712 { /// @param _spotMarketProxy Synthetix v3 spot market proxy contract /// @param _sUSDProxy Synthetix v3 sUSD contract /// @param _oracle pyth oracle contract used to get asset prices + /// @param _trustedForwarder trusted forwarder contract used for meta transactions constructor( address _perpsMarketProxy, address _spotMarketProxy, address _sUSDProxy, - address _oracle - ) { + address _oracle, + address _trustedForwarder + ) ERC2771Context(_trustedForwarder) { if (_perpsMarketProxy == address(0)) revert ZeroAddress(); if (_spotMarketProxy == address(0)) revert ZeroAddress(); if (_sUSDProxy == address(0)) revert ZeroAddress(); if (_oracle == address(0)) revert ZeroAddress(); + if (_trustedForwarder == address(0)) revert ZeroAddress(); PERPS_MARKET_PROXY = IPerpsMarketProxy(_perpsMarketProxy); SPOT_MARKET_PROXY = ISpotMarketProxy(_spotMarketProxy); @@ -167,7 +172,7 @@ contract Engine is IEngine, Multicallable, EIP712 { uint256 _wordPos, uint256 _mask ) external override { - if (_isAccountOwnerOrDelegate(_accountId, msg.sender)) { + if (_isAccountOwnerOrDelegate(_accountId, _msgSender())) { /// @dev using bitwise OR to set the bit at the bit position /// bitmap = .......10001 /// mask = .......00110 @@ -280,14 +285,16 @@ contract Engine is IEngine, Multicallable, EIP712 { ) external override { IERC20 synth = IERC20(_getSynthAddress(_synthMarketId)); + address caller = _msgSender(); + if (_amount > 0) { _depositCollateral( - msg.sender, synth, _accountId, _synthMarketId, _amount + caller, synth, _accountId, _synthMarketId, _amount ); } else { - if (!isAccountOwner(_accountId, msg.sender)) revert Unauthorized(); + if (!isAccountOwner(_accountId, caller)) revert Unauthorized(); _withdrawCollateral( - msg.sender, synth, _accountId, _synthMarketId, _amount + caller, synth, _accountId, _synthMarketId, _amount ); } } @@ -352,7 +359,7 @@ contract Engine is IEngine, Multicallable, EIP712 { returns (IPerpsMarketProxy.Data memory retOrder, uint256 fees) { /// @dev only the account owner can withdraw collateral - if (_isAccountOwnerOrDelegate(_accountId, msg.sender)) { + if (_isAccountOwnerOrDelegate(_accountId, _msgSender())) { (retOrder, fees) = _commitOrder({ _perpsMarketId: _perpsMarketId, _accountId: _accountId, @@ -468,7 +475,7 @@ contract Engine is IEngine, Multicallable, EIP712 { /// @dev withdraw conditional order fee from account prior to executing order _withdrawCollateral({ - _to: msg.sender, + _to: _msgSender(), _synth: SUSD, _accountId: _co.orderDetails.accountId, _synthMarketId: USD_SYNTH_ID, @@ -511,7 +518,7 @@ contract Engine is IEngine, Multicallable, EIP712 { } else { // if the order does not require verification, then the caller // must be the trusted executor defined by "trustedExecutor" - if (msg.sender != _co.trustedExecutor) return false; + if (_msgSender() != _co.trustedExecutor) return false; } return true; diff --git a/src/interfaces/synthetix/IERC7412.sol b/src/interfaces/synthetix/IERC7412.sol new file mode 100644 index 00000000..c36fb921 --- /dev/null +++ b/src/interfaces/synthetix/IERC7412.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.18; + +/// @title ERC-7412 Off-Chain Data Retrieval Contract +/// @author Synthetix +interface IERC7412 { + /// @dev Emitted when an oracle is requested to provide data. + /// Upon receipt of this error, a wallet client + /// should automatically resolve the requested oracle data + /// and call fulfillOracleQuery. + /// @param oracleContract The address of the oracle contract + /// (which is also the fulfillment contract). + /// @param oracleQuery The query to be sent to the off-chain interface. + error OracleDataRequired(address oracleContract, bytes oracleQuery); + + /// @dev Emitted when the recently posted oracle data requires + /// a fee to be paid. Upon receipt of this error, + /// a wallet client should attach the requested feeAmount + /// to the most recently posted oracle data transaction + error FeeRequired(uint256 feeAmount); + + /// @dev Returns a human-readable identifier of the oracle + /// contract. This should map to a URL and API + /// key on the client side. + /// @return The oracle identifier. + function oracleId() external view returns (bytes32); + + /// @dev Upon resolving the oracle query, the client should + /// call this function to post the data to the + /// blockchain. + /// @param signedOffchainData The data that was returned + /// from the off-chain interface, signed by the oracle. + function fulfillOracleQuery(bytes calldata signedOffchainData) + external + payable; +} diff --git a/src/utils/EIP7412.sol b/src/utils/EIP7412.sol new file mode 100644 index 00000000..82b5e29c --- /dev/null +++ b/src/utils/EIP7412.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.18; + +import {IERC7412} from "src/interfaces/synthetix/IERC7412.sol"; + +/// @title Kwenta Smart Margin v3: EIP-7412 Utility Contract +/// @notice Responsible for fulfilling EIP-7412 oracle queries +/// @author JaredBorders (jaredborders@pm.me) +contract EIP7412 { + /// @notice Fulfill an EIP-7412 oracle query + /// @param EIP7412Implementer The address of the EIP-7412 implementer + /// @param signedOffchainData The data that was returned + /// from the off-chain interface, signed by the oracle + function fulfillOracleQuery( + address EIP7412Implementer, + bytes calldata signedOffchainData + ) external payable { + IERC7412(EIP7412Implementer).fulfillOracleQuery(signedOffchainData); + } +} diff --git a/src/utils/ERC2771Context.sol b/src/utils/ERC2771Context.sol new file mode 100644 index 00000000..d70b3edd --- /dev/null +++ b/src/utils/ERC2771Context.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol) +pragma solidity 0.8.18; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +/** + * @dev Context variant with ERC2771 support. + * + * WARNING: Avoid using this pattern in contracts that rely in a specific calldata length as they'll + * be affected by any forwarder whose `msg.data` is suffixed with the `from` address according to the ERC2771 + * specification adding the address size in bytes (20) to the calldata size. An example of an unexpected + * behavior could be an unintended fallback (or another function) invocation while trying to invoke the `receive` + * function only accessible if `msg.data.length == 0`. + */ +contract ERC2771Context is Context { + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address private immutable _trustedForwarder; + + /** + * @dev Initializes the contract with a trusted forwarder, which will be able to + * invoke functions on this contract on behalf of other accounts. + * + * NOTE: The trusted forwarder can be replaced by overriding {trustedForwarder}. + */ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address trustedForwarder_) { + _trustedForwarder = trustedForwarder_; + } + + /** + * @dev Returns the address of the trusted forwarder. + */ + function trustedForwarder() public view returns (address) { + return _trustedForwarder; + } + + /** + * @dev Indicates whether any particular address is the trusted forwarder. + */ + function isTrustedForwarder(address forwarder) public view returns (bool) { + return forwarder == trustedForwarder(); + } + + /** + * @dev Override for `msg.sender`. Defaults to the original `msg.sender` whenever + * a call is not performed by the trusted forwarder or the calldata length is less than + * 20 bytes (an address length). + */ + function _msgSender() internal view override returns (address sender) { + if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) { + // The assembly code is more direct than the Solidity version using `abi.decode`. + /// @solidity memory-safe-assembly + assembly { + sender := shr(96, calldataload(sub(calldatasize(), 20))) + } + } else { + return super._msgSender(); + } + } + + /** + * @dev Override for `msg.data`. Defaults to the original `msg.data` whenever + * a call is not performed by the trusted forwarder or the calldata length is less than + * 20 bytes (an address length). + */ + function _msgData() internal view override returns (bytes calldata) { + if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) { + return msg.data[:msg.data.length - 20]; + } else { + return super._msgData(); + } + } +} diff --git a/test/Deployment.t.sol b/test/Deployment.t.sol index 6a76d2af..7cff9855 100644 --- a/test/Deployment.t.sol +++ b/test/Deployment.t.sol @@ -11,7 +11,8 @@ contract DeploymentTest is Test, Setup { perpsMarketProxy: address(0x1), spotMarketProxy: address(0x2), sUSDProxy: address(0x3), - oracle: address(0x4) + oracle: address(0x4), + trustedForwarder: address(0x5) }); assertTrue(address(engine) != address(0x0)); @@ -24,7 +25,8 @@ contract DeploymentTest is Test, Setup { perpsMarketProxy: address(0), spotMarketProxy: address(0x2), sUSDProxy: address(0x3), - oracle: address(0x4) + oracle: address(0x4), + trustedForwarder: address(0x5) }); } @@ -35,7 +37,8 @@ contract DeploymentTest is Test, Setup { perpsMarketProxy: address(0x1), spotMarketProxy: address(0), sUSDProxy: address(0x3), - oracle: address(0x4) + oracle: address(0x4), + trustedForwarder: address(0x5) }); } @@ -46,7 +49,8 @@ contract DeploymentTest is Test, Setup { perpsMarketProxy: address(0x1), spotMarketProxy: address(0x2), sUSDProxy: address(0), - oracle: address(0x4) + oracle: address(0x4), + trustedForwarder: address(0x5) }); } @@ -57,7 +61,20 @@ contract DeploymentTest is Test, Setup { perpsMarketProxy: address(0x1), spotMarketProxy: address(0x2), sUSDProxy: address(0x3), - oracle: address(0) + oracle: address(0), + trustedForwarder: address(0x5) + }); + } + + function test_deploy_trusted_forwarder_zero_address() public { + vm.expectRevert(abi.encodeWithSelector(IEngine.ZeroAddress.selector)); + + Setup.deploySystem({ + perpsMarketProxy: address(0x1), + spotMarketProxy: address(0x2), + sUSDProxy: address(0x3), + oracle: address(0x4), + trustedForwarder: address(0) }); } } diff --git a/test/EIP7412.t.sol b/test/EIP7412.t.sol new file mode 100644 index 00000000..8c98ca2c --- /dev/null +++ b/test/EIP7412.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.18; + +import {EIP7412} from "src/utils/EIP7412.sol"; +import {SynthetixMock} from "test/utils/mocks/SynthetixMock.sol"; +import {Test} from "lib/forge-std/src/Test.sol"; + +contract EIP7412Test is Test, SynthetixMock { + function test_fulfillOracleQuery(bytes calldata signedOffchainData) + public + { + address payable mock_eip7412_implementer = payable(address(0xE19)); + + mock_fulfillOracleQuery(mock_eip7412_implementer, signedOffchainData); + + EIP7412 eip7412 = new EIP7412(); + + uint256 preBalance = address(this).balance; + + (bool success,) = address(eip7412).call{value: 5 wei}( + abi.encodeWithSelector( + EIP7412.fulfillOracleQuery.selector, + mock_eip7412_implementer, + signedOffchainData + ) + ); + + assertTrue(success); + assertLt(address(this).balance, preBalance); + } +} diff --git a/test/utils/Bootstrap.sol b/test/utils/Bootstrap.sol index bad3923e..c5be4cec 100644 --- a/test/utils/Bootstrap.sol +++ b/test/utils/Bootstrap.sol @@ -107,14 +107,16 @@ contract BootstrapOptimism is Setup, OptimismParameters { perpsMarketProxy: PERPS_MARKET_PROXY, spotMarketProxy: SPOT_MARKET_PROXY, sUSDProxy: USD_PROXY, - oracle: PYTH + oracle: PYTH, + trustedForwarder: TRUSTED_FORWARDER }); EngineExposed engineExposed = new EngineExposed({ _perpsMarketProxy: PERPS_MARKET_PROXY, _spotMarketProxy: SPOT_MARKET_PROXY, _sUSDProxy: USD_PROXY, - _oracle: PYTH + _oracle: PYTH, + _trustedForwarder: TRUSTED_FORWARDER }); return ( @@ -137,14 +139,16 @@ contract BootstrapOptimismGoerli is Setup, OptimismGoerliParameters { perpsMarketProxy: PERPS_MARKET_PROXY, spotMarketProxy: SPOT_MARKET_PROXY, sUSDProxy: USD_PROXY, - oracle: PYTH + oracle: PYTH, + trustedForwarder: TRUSTED_FORWARDER }); EngineExposed engineExposed = new EngineExposed({ _perpsMarketProxy: PERPS_MARKET_PROXY, _spotMarketProxy: SPOT_MARKET_PROXY, _sUSDProxy: USD_PROXY, - _oracle: PYTH + _oracle: PYTH, + _trustedForwarder: TRUSTED_FORWARDER }); return ( diff --git a/test/utils/exposed/EngineExposed.sol b/test/utils/exposed/EngineExposed.sol index 1bb9e378..ae72e387 100644 --- a/test/utils/exposed/EngineExposed.sol +++ b/test/utils/exposed/EngineExposed.sol @@ -10,8 +10,17 @@ contract EngineExposed is Engine { address _perpsMarketProxy, address _spotMarketProxy, address _sUSDProxy, - address _oracle - ) Engine(_perpsMarketProxy, _spotMarketProxy, _sUSDProxy, _oracle) {} + address _oracle, + address _trustedForwarder + ) + Engine( + _perpsMarketProxy, + _spotMarketProxy, + _sUSDProxy, + _oracle, + _trustedForwarder + ) + {} function getSynthAddress(uint128 synthMarketId) public diff --git a/test/utils/mocks/SynthetixMock.sol b/test/utils/mocks/SynthetixMock.sol index a52c8a77..e8cf00f2 100644 --- a/test/utils/mocks/SynthetixMock.sol +++ b/test/utils/mocks/SynthetixMock.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.18; +import {EIP7412} from "src/utils/EIP7412.sol"; import {IPerpsMarketProxy} from "src/interfaces/synthetix/IPerpsMarketProxy.sol"; import {Test} from "lib/forge-std/src/Test.sol"; @@ -49,4 +50,17 @@ contract SynthetixMock is Test { abi.encode(maxMarketSize) ); } + + function mock_fulfillOracleQuery( + address EIP7412Implementer, + bytes calldata signedOffchainData + ) public { + vm.mockCall( + EIP7412Implementer, + abi.encodeWithSelector( + EIP7412.fulfillOracleQuery.selector, signedOffchainData + ), + abi.encode() + ); + } }