From 0ffc2a0419b4abd8e8dcd34010d61b9ee99d2ef7 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 4 Jul 2024 13:27:58 +0300 Subject: [PATCH 01/29] implement intent factory --- src/Helpers/Intent.sol | 85 +++++++++++++ src/Interfaces/IIntent.sol | 17 +++ src/Periphery/IntentFactory.sol | 64 ++++++++++ test/solidity/Periphery/IntentFactory.t.sol | 128 ++++++++++++++++++++ 4 files changed, 294 insertions(+) create mode 100644 src/Helpers/Intent.sol create mode 100644 src/Interfaces/IIntent.sol create mode 100644 src/Periphery/IntentFactory.sol create mode 100644 test/solidity/Periphery/IntentFactory.t.sol diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol new file mode 100644 index 000000000..f0b06680e --- /dev/null +++ b/src/Helpers/Intent.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { LibClone } from "solady/utils/LibClone.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { IIntent } from "../Interfaces/IIntent.sol"; + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); +} + +/// @title Intent +/// @author LI.FI (https://li.fi) +/// @notice Intent contract that can execute arbitrary calls. +/// @custom:version 1.0.0 +contract Intent { + bytes32 public intentId; + bytes32 public salt; + address public receiver; + address public immutable implementation; + address public factory; + address public tokenOut; + uint256 public amoutOutMin; + bool public executed = false; + + constructor() { + implementation = address(this); + } + + /// @notice Initializes the intent with the given parameters. + /// @param _initData The init data. + function init(IIntent.InitData calldata _initData) external { + bytes32 _salt = keccak256(abi.encode(_initData)); + address predictedAddress = LibClone.predictDeterministicAddress( + implementation, + _salt, + msg.sender + ); + require( + address(this) == predictedAddress, + "Intent: invalid init params" + ); + + intentId = _initData.intentId; + receiver = _initData.receiver; + tokenOut = _initData.tokenOut; + amoutOutMin = _initData.amoutOutMin; + } + + /// @notice Executes the intent with the given calls. + /// @param calls The calls to execute. + function execute(IIntent.Call[] calldata calls) external { + require(!executed, "Intent: already executed"); + executed = true; + + for (uint256 i = 0; i < calls.length; i++) { + (bool success, ) = calls[i].to.call{ value: calls[i].value }( + calls[i].data + ); + require(success, "Intent: call failed"); + } + + require( + IERC20(tokenOut).balanceOf(address(this)) >= amoutOutMin, + "Intent: insufficient output amount" + ); + if (tokenOut == address(0)) { + SafeTransferLib.safeTransferAllETH(receiver); + return; + } + SafeTransferLib.safeTransferAll(tokenOut, receiver); + } + + /// @notice Withdraws all the tokens. + /// @param tokens The tokens to withdraw. + function withdrawAll(address[] calldata tokens) external { + for (uint256 i = 0; i < tokens.length; i++) { + if (tokens[i] == address(0)) { + SafeTransferLib.safeTransferAllETH(receiver); + continue; + } + SafeTransferLib.safeTransferAll(tokens[i], receiver); + } + } +} diff --git a/src/Interfaces/IIntent.sol b/src/Interfaces/IIntent.sol new file mode 100644 index 000000000..255817534 --- /dev/null +++ b/src/Interfaces/IIntent.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +interface IIntent { + struct Call { + address to; + uint256 value; + bytes data; + } + + struct InitData { + bytes32 intentId; + address receiver; + address tokenOut; + uint256 amoutOutMin; + } +} diff --git a/src/Periphery/IntentFactory.sol b/src/Periphery/IntentFactory.sol new file mode 100644 index 000000000..46d6c5134 --- /dev/null +++ b/src/Periphery/IntentFactory.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { LibClone } from "solady/utils/LibClone.sol"; +import { IIntent } from "../Interfaces/IIntent.sol"; +import { Intent } from "../Helpers/Intent.sol"; + +/// @title Intent Factory +/// @author LI.FI (https://li.fi) +/// @notice Deploys minimal proxies of "intents" that can execute arbitrary calls. +/// @custom:version 1.0.0 +contract IntentFactory { + /// Storage /// + + address public immutable implementation; + + /// Constructor /// + + constructor(address _implementation) { + implementation = _implementation; + } + + /// External Functions /// + + /// @notice Deploys a new intent and executes the given calls. + /// @param _initData The init data. + /// @param _calls The calls to execute. + function deployAndExecuteIntent( + IIntent.InitData calldata _initData, + IIntent.Call[] calldata _calls + ) external { + bytes32 salt = keccak256(abi.encode(_initData)); + address clone = LibClone.cloneDeterministic(implementation, salt); + Intent(clone).init(_initData); + Intent(clone).execute(_calls); + } + + /// @notice Deploys a new intent and withdraws all the tokens. + /// @param _initData The init data. + /// @param tokens The tokens to withdraw. + function deployAndWithdrawAll( + IIntent.InitData calldata _initData, + address[] calldata tokens + ) external { + bytes32 salt = keccak256(abi.encode(_initData)); + address clone = LibClone.cloneDeterministic(implementation, salt); + Intent(clone).init(_initData); + Intent(clone).withdrawAll(tokens); + } + + /// @notice Predicts the address of the intent. + /// @param _initData The init data. + function getIntentAddress( + IIntent.InitData calldata _initData + ) external view returns (address) { + bytes32 salt = keccak256(abi.encode(_initData)); + return + LibClone.predictDeterministicAddress( + implementation, + salt, + address(this) + ); + } +} diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol new file mode 100644 index 000000000..fd0d5353f --- /dev/null +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import { Test, console } from "forge-std/Test.sol"; +import { Intent } from "lifi/Helpers/Intent.sol"; +import { IIntent } from "lifi/Interfaces/IIntent.sol"; +import { IntentFactory } from "lifi/Periphery/IntentFactory.sol"; +import { TestToken } from "../utils/TestToken.sol"; +import { TestAMM } from "../utils/TestAMM.sol"; + +contract IntentFactoryTest is Test { + Intent public implementation; + IntentFactory public factory; + TestAMM public amm; + TestToken public tokenA; + TestToken public tokenB; + address public alice; + + function setUp() public { + (implementation, factory) = deploy(); + amm = new TestAMM(); + tokenA = new TestToken("TokenA", "TKNA", 18); + tokenB = new TestToken("TokenB", "TKNB", 18); + alice = makeAddr("alice"); + } + + function deploy() public returns (Intent, IntentFactory) { + address _implementation = address(new Intent()); + IntentFactory _factory = new IntentFactory(_implementation); + return (Intent(_implementation), _factory); + } + + function test_can_deposit_and_execute_swap() public { + tokenA.mint(alice, 1000); + bytes32 intentId = keccak256("intentId"); + + // Compute the address of the intent + address intentClone = factory.getIntentAddress( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amoutOutMin: 100 + }) + ); + + // Send tokens to the precomputed address + vm.prank(alice); + tokenA.transfer(intentClone, 1000); + + IIntent.Call[] memory calls = new IIntent.Call[](2); + + // get approve calldata + bytes memory approveCalldata = abi.encodeWithSignature( + "approve(address,uint256)", + address(amm), + 1000 + ); + calls[0] = IIntent.Call({ + to: address(tokenA), + value: 0, + data: approveCalldata + }); + + // get swap calldata + bytes memory swapCalldata = abi.encodeWithSignature( + "swap(address,uint256,address,uint256)", + address(tokenA), + 1000, + address(tokenB), + 100 + ); + calls[1] = IIntent.Call({ + to: address(amm), + value: 0, + data: swapCalldata + }); + + // execute the intent + factory.deployAndExecuteIntent( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amoutOutMin: 100 + }), + calls + ); + + // assertions + assertEq(tokenB.balanceOf(alice), 100); + assertEq(tokenA.balanceOf(alice), 0); + assertEq(tokenB.balanceOf(intentClone), 0); + assertEq(tokenA.balanceOf(intentClone), 0); + } + + function test_can_deposit_and_withdraw_all() public { + tokenA.mint(alice, 1000); + bytes32 intentId = keccak256("intentId"); + // Compute the address of the intent + address intentClone = factory.getIntentAddress( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amoutOutMin: 100 + }) + ); + // Send tokens to the precomputed address + vm.prank(alice); + tokenA.transfer(intentClone, 1000); + // execute the intent + address[] memory tokens = new address[](1); + tokens[0] = address(tokenA); + factory.deployAndWithdrawAll( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amoutOutMin: 100 + }), + tokens + ); + // assertions + assertEq(tokenA.balanceOf(alice), 1000); + assertEq(tokenA.balanceOf(intentClone), 0); + } +} From ff3000b9b15089b74ce134ee2679c915d3a2d8d7 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 4 Jul 2024 17:23:32 +0300 Subject: [PATCH 02/29] Deploy to staging on Arbitrum --- deployments/_deployments_log_file.json | 16 ++++++++++ deployments/arbitrum.staging.json | 3 +- .../deploy/facets/DeployIntentFactory.s.sol | 31 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 script/deploy/facets/DeployIntentFactory.s.sol diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 82e246e26..975840e43 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -20490,5 +20490,21 @@ ] } } + }, + "IntentFactory": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xf67dfdA04820C17E12C5C08DB3a59859cF08508D", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2024-07-04 17:12:51", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c7f2cf4845c6db0e1a1e91ed41bcd0fcc1b0e141", + "SALT": "", + "VERIFIED": "true" + } + ] + } + } } } diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index a6e1c552d..cee01ad10 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -34,5 +34,6 @@ "SymbiosisFacet": "0xb590b3B312f3C73621aa1E363841c8baecc2E712", "DeBridgeDlnFacet": "0xE500dED7b9C9f1020870B7a6Db076Dbd892C0fea", "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", - "StandardizedCallFacet": "0x637Ac9AddC9C38b3F52878E11620a9060DC71d8B" + "StandardizedCallFacet": "0x637Ac9AddC9C38b3F52878E11620a9060DC71d8B", + "IntentFactory": "0xf67dfdA04820C17E12C5C08DB3a59859cF08508D" } \ No newline at end of file diff --git a/script/deploy/facets/DeployIntentFactory.s.sol b/script/deploy/facets/DeployIntentFactory.s.sol new file mode 100644 index 000000000..77840611f --- /dev/null +++ b/script/deploy/facets/DeployIntentFactory.s.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; +import { IntentFactory } from "lifi/Periphery/IntentFactory.sol"; +import { Intent } from "lifi/Helpers/Intent.sol"; + +contract DeployScript is DeployScriptBase { + Intent implementation; + + constructor() DeployScriptBase("IntentFactory") {} + + function run() + public + returns (IntentFactory deployed, bytes memory constructorArgs) + { + implementation = new Intent(); + constructorArgs = getConstructorArgs(); + + deployed = IntentFactory(deploy(type(IntentFactory).creationCode)); + } + + function getConstructorArgs() + internal + view + override + returns (bytes memory) + { + return abi.encode(address(implementation)); + } +} From 27dd18ad6d5341722c12542d14a9d46963afa0f9 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 8 Jul 2024 15:10:54 +0300 Subject: [PATCH 03/29] Fix typo --- script/demoScripts/demoIntentFactory.ts | 73 +++++++++++++++++++++ src/Helpers/Intent.sol | 6 +- src/Interfaces/IIntent.sol | 2 +- test/solidity/Periphery/IntentFactory.t.sol | 8 +-- 4 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 script/demoScripts/demoIntentFactory.ts diff --git a/script/demoScripts/demoIntentFactory.ts b/script/demoScripts/demoIntentFactory.ts new file mode 100644 index 000000000..1d76ac7e2 --- /dev/null +++ b/script/demoScripts/demoIntentFactory.ts @@ -0,0 +1,73 @@ +import { arbitrum } from 'viem/chains' +import { defineCommand, runMain } from 'citty' +import * as Deployments from '../../deployments/arbitrum.staging.json' +import * as IntentFactory from '../../out/IntentFactory.sol/IntentFactory.json' +import { + Address, + Hex, + createPublicClient, + createWalletClient, + http, + keccak256, + parseUnits, + toHex, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +const INTENT_FACTORY_ADDReSS = Deployments.IntentFactory as Address +const ABI = IntentFactory.abi + +const main = defineCommand({ + meta: { + name: 'propose-to-safe', + description: 'Propose a transaction to a Gnosis Safe', + }, + args: { + privateKey: { + type: 'string', + description: 'Private key of the signer', + required: true, + }, + }, + async run({ args }) { + // Read client + const publicClient = createPublicClient({ + chain: arbitrum, + transport: http(), + }) + + // Write client + const walletClient = createWalletClient({ + account: privateKeyToAccount(args.privateKey as Hex), + chain: arbitrum, + transport: http(), + }) + + const intentFactory = { + address: INTENT_FACTORY_ADDReSS, + abi: ABI, + } + + // struct InitData { + // bytes32 intentId; + // address receiver; + // address tokenOut; + // uint256 amoutOutMin; + // } + const predictedIntentAddress = await publicClient.readContract({ + ...intentFactory, + functionName: 'getIntentAddress', + args: [ + { + intentId: keccak256(toHex(parseInt(Math.random().toString()))), + receiver: privateKeyToAccount(args.privateKey as Hex).address, + tokenOut: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + amoutOutMin: parseUnits('10', 6), + }, + ], + }) + console.log(predictedIntentAddress) + }, +}) + +runMain(main) diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index f0b06680e..fbbfa9c3f 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -20,7 +20,7 @@ contract Intent { address public immutable implementation; address public factory; address public tokenOut; - uint256 public amoutOutMin; + uint256 public amountOutMin; bool public executed = false; constructor() { @@ -44,7 +44,7 @@ contract Intent { intentId = _initData.intentId; receiver = _initData.receiver; tokenOut = _initData.tokenOut; - amoutOutMin = _initData.amoutOutMin; + amountOutMin = _initData.amountOutMin; } /// @notice Executes the intent with the given calls. @@ -61,7 +61,7 @@ contract Intent { } require( - IERC20(tokenOut).balanceOf(address(this)) >= amoutOutMin, + IERC20(tokenOut).balanceOf(address(this)) >= amountOutMin, "Intent: insufficient output amount" ); if (tokenOut == address(0)) { diff --git a/src/Interfaces/IIntent.sol b/src/Interfaces/IIntent.sol index 255817534..6b957d00d 100644 --- a/src/Interfaces/IIntent.sol +++ b/src/Interfaces/IIntent.sol @@ -12,6 +12,6 @@ interface IIntent { bytes32 intentId; address receiver; address tokenOut; - uint256 amoutOutMin; + uint256 amountOutMin; } } diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index fd0d5353f..93851d7d3 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -40,7 +40,7 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amoutOutMin: 100 + amountOutMin: 100 }) ); @@ -82,7 +82,7 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amoutOutMin: 100 + amountOutMin: 100 }), calls ); @@ -103,7 +103,7 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amoutOutMin: 100 + amountOutMin: 100 }) ); // Send tokens to the precomputed address @@ -117,7 +117,7 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amoutOutMin: 100 + amountOutMin: 100 }), tokens ); From ac2cb2c7713f15bf3eff82f7af45256427008a35 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 8 Jul 2024 15:16:18 +0300 Subject: [PATCH 04/29] Redeploy to staging --- deployments/_deployments_log_file.json | 6 +++--- deployments/arbitrum.diamond.staging.json | 4 +++- deployments/arbitrum.staging.json | 2 +- script/demoScripts/demoIntentFactory.ts | 8 +------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 975840e43..f59283a82 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -20496,12 +20496,12 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xf67dfdA04820C17E12C5C08DB3a59859cF08508D", + "ADDRESS": "0xd5F7C3CB19610Ec98d5b4eA991E28d6522542Bfd", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-07-04 17:12:51", + "TIMESTAMP": "2024-07-08 15:15:41", "CONSTRUCTOR_ARGS": "0x000000000000000000000000c7f2cf4845c6db0e1a1e91ed41bcd0fcc1b0e141", "SALT": "", - "VERIFIED": "true" + "VERIFIED": "false" } ] } diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index 0d3ca3de6..29a516f61 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -127,7 +127,9 @@ "Receiver": "0x59B341fF54543D66C7393FfD2A050E256c97669E", "RelayerCelerIM": "0x9d3573b1d85112446593f617f1f3eb5ec1778D27", "ServiceFeeCollector": "0x9cc3164f01ED3796Fdf7Da538484D634608D2203", - "TokenWrapper": "" + "TokenWrapper": "", + "IntentFactory": "", + "ReceiverStargateV2": "" } } } \ No newline at end of file diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index cee01ad10..48212d6a2 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -35,5 +35,5 @@ "DeBridgeDlnFacet": "0xE500dED7b9C9f1020870B7a6Db076Dbd892C0fea", "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "StandardizedCallFacet": "0x637Ac9AddC9C38b3F52878E11620a9060DC71d8B", - "IntentFactory": "0xf67dfdA04820C17E12C5C08DB3a59859cF08508D" + "IntentFactory": "0xd5F7C3CB19610Ec98d5b4eA991E28d6522542Bfd" } \ No newline at end of file diff --git a/script/demoScripts/demoIntentFactory.ts b/script/demoScripts/demoIntentFactory.ts index 1d76ac7e2..bfaf68184 100644 --- a/script/demoScripts/demoIntentFactory.ts +++ b/script/demoScripts/demoIntentFactory.ts @@ -48,12 +48,6 @@ const main = defineCommand({ abi: ABI, } - // struct InitData { - // bytes32 intentId; - // address receiver; - // address tokenOut; - // uint256 amoutOutMin; - // } const predictedIntentAddress = await publicClient.readContract({ ...intentFactory, functionName: 'getIntentAddress', @@ -62,7 +56,7 @@ const main = defineCommand({ intentId: keccak256(toHex(parseInt(Math.random().toString()))), receiver: privateKeyToAccount(args.privateKey as Hex).address, tokenOut: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', - amoutOutMin: parseUnits('10', 6), + amountOutMin: parseUnits('10', 6), }, ], }) From fa392a01857af9d7b5713bc6ec77e164e49f1a02 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 8 Jul 2024 17:45:48 +0300 Subject: [PATCH 05/29] Refactor --- script/demoScripts/demoIntentFactory.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/script/demoScripts/demoIntentFactory.ts b/script/demoScripts/demoIntentFactory.ts index bfaf68184..0c22cbb3b 100644 --- a/script/demoScripts/demoIntentFactory.ts +++ b/script/demoScripts/demoIntentFactory.ts @@ -30,6 +30,8 @@ const main = defineCommand({ }, }, async run({ args }) { + const { privateKey } = args + const account = privateKeyToAccount(`0x${privateKey}`) // Read client const publicClient = createPublicClient({ chain: arbitrum, @@ -38,7 +40,7 @@ const main = defineCommand({ // Write client const walletClient = createWalletClient({ - account: privateKeyToAccount(args.privateKey as Hex), + account, chain: arbitrum, transport: http(), }) @@ -54,13 +56,17 @@ const main = defineCommand({ args: [ { intentId: keccak256(toHex(parseInt(Math.random().toString()))), - receiver: privateKeyToAccount(args.privateKey as Hex).address, + receiver: account.address, tokenOut: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', amountOutMin: parseUnits('10', 6), }, ], }) console.log(predictedIntentAddress) + + // TODO: Get quote from LIFI API for simple swap from DAI to USDC + // TODO: Send DAI to predictedIntentAddress + // TODO: Execute the swap }, }) From 529e309b61777d8247c93c766fc02ff35136f500 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 8 Jul 2024 18:01:27 +0300 Subject: [PATCH 06/29] Get LIFI quote --- package.json | 1 + script/demoScripts/demoIntentFactory.ts | 19 +- yarn.lock | 296 ++++++++++++------------ 3 files changed, 168 insertions(+), 148 deletions(-) diff --git a/package.json b/package.json index c33c048c4..380b88bd7 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "@arbitrum/sdk": "^3.0.0", "@hop-protocol/sdk": "0.0.1-beta.310", "@layerzerolabs/lz-v2-utilities": "^2.3.21", + "@lifi/sdk": "^3.0.0", "@safe-global/api-kit": "^2.3.1", "@safe-global/protocol-kit": "^3.1.0", "@safe-global/safe-apps-sdk": "^9.0.0", diff --git a/script/demoScripts/demoIntentFactory.ts b/script/demoScripts/demoIntentFactory.ts index 0c22cbb3b..776a21c14 100644 --- a/script/demoScripts/demoIntentFactory.ts +++ b/script/demoScripts/demoIntentFactory.ts @@ -13,9 +13,12 @@ import { toHex, } from 'viem' import { privateKeyToAccount } from 'viem/accounts' +import { ChainId, getQuote } from '@lifi/sdk' const INTENT_FACTORY_ADDReSS = Deployments.IntentFactory as Address const ABI = IntentFactory.abi +const DAI = '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1' +const USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' const main = defineCommand({ meta: { @@ -50,7 +53,7 @@ const main = defineCommand({ abi: ABI, } - const predictedIntentAddress = await publicClient.readContract({ + const predictedIntentAddress: Address = (await publicClient.readContract({ ...intentFactory, functionName: 'getIntentAddress', args: [ @@ -61,10 +64,20 @@ const main = defineCommand({ amountOutMin: parseUnits('10', 6), }, ], - }) + })) as Address console.log(predictedIntentAddress) - // TODO: Get quote from LIFI API for simple swap from DAI to USDC + const quote = await getQuote({ + fromAddress: predictedIntentAddress, + toAddress: account.address, + fromChain: ChainId.ARB, + toChain: ChainId.ARB, + fromToken: DAI, + toToken: USDC, + fromAmount: '10000000000000000000', + }) + + console.log(quote) // TODO: Send DAI to predictedIntentAddress // TODO: Execute the swap }, diff --git a/yarn.lock b/yarn.lock index 3f3afbef3..2e942aed9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,7 +43,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/runtime@^7.24.6": +"@babel/runtime@^7.24.6", "@babel/runtime@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== @@ -781,6 +781,22 @@ "@solana/web3.js" "^1.92.1" tiny-invariant "^1.3.1" +"@lifi/sdk@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@lifi/sdk/-/sdk-3.0.0.tgz#cfb0a885279744571ff1ec11107877fa6a929b9b" + integrity sha512-Xinus12HOlQqJm/tdl3uNt0/Mp2+x3lK5cFPo2OsfLHmWirtc+tax1ExlUcDq5+ODURwKUrbo9WisHqa8DB1uA== + dependencies: + "@lifi/types" "^13.17.0" + "@solana/wallet-adapter-base" "^0.9.23" + "@solana/web3.js" "^1.93.2" + eth-rpc-errors "^4.0.3" + viem "^2.16.2" + +"@lifi/types@^13.17.0": + version "13.17.1" + resolved "https://registry.yarnpkg.com/@lifi/types/-/types-13.17.1.tgz#4745615ac6dd7cfc15c9b864d7496409d7ee0487" + integrity sha512-w3UCUzHMCMDq4CjxlawBQsAkSv22z2Kflb/NbhnhzKrMqtFAaxgW+82hEGs+Vlbyrzi6F6M2PEkkayzJioSCBQ== + "@maticnetwork/maticjs@^2.0.38": version "2.0.51" resolved "https://registry.yarnpkg.com/@maticnetwork/maticjs/-/maticjs-2.0.51.tgz#4fe82150384d6241ed2276bba4ad7c111445593f" @@ -865,17 +881,17 @@ dependencies: "@noble/hashes" "1.3.3" -"@noble/curves@^1.4.0": +"@noble/curves@1.4.0", "@noble/curves@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" - integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== +"@noble/curves@~1.4.0": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" + integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== dependencies: "@noble/hashes" "1.4.0" @@ -894,8 +910,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== -"@noble/hashes@1.4.0", "@noble/hashes@^1.3.3", "@noble/hashes@^1.4.0": -"@noble/hashes@1.4.0", "@noble/hashes@^1.3.3", "@noble/hashes@^1.4.0": +"@noble/hashes@1.4.0", "@noble/hashes@^1.3.3", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== @@ -1280,7 +1295,7 @@ resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.21.1.tgz#984ec2d3d4211caf6a96786ab922b39909093538" integrity sha512-7nakIjcRSs6781LkizYpIfXh1DYlkUDqyALciqz/BjFU/S97sVjZdL4cuKsG9NEarytE+f6p0Qbq2Bo1aocVUA== -"@scure/base@~1.1.0", "@scure/base@~1.1.2": +"@scure/base@~1.1.0", "@scure/base@~1.1.2", "@scure/base@~1.1.6": version "1.1.7" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== @@ -1317,6 +1332,15 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.4" +"@scure/bip32@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" + integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== + dependencies: + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@scure/bip39@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.0.tgz#92f11d095bae025f166bef3defcc5bf4945d419a" @@ -1341,6 +1365,14 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.4" +"@scure/bip39@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3" + integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ== + dependencies: + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -1419,13 +1451,52 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== -"@solana/buffer-layout@^4.0.1": +"@solana/buffer-layout@^4 || ^3", "@solana/buffer-layout@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== dependencies: buffer "~6.0.3" +"@solana/wallet-adapter-base@^0.9.23": + version "0.9.23" + resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.23.tgz#3b17c28afd44e173f44f658bf9700fd637e12a11" + integrity sha512-apqMuYwFp1jFi55NxDfvXUX2x1T0Zh07MxhZ/nCCTGys5raSfYUh82zen2BLv8BSDj/JxZ2P/s7jrQZGrX8uAw== + dependencies: + "@solana/wallet-standard-features" "^1.1.0" + "@wallet-standard/base" "^1.0.1" + "@wallet-standard/features" "^1.0.3" + eventemitter3 "^4.0.7" + +"@solana/wallet-standard-features@^1.1.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@solana/wallet-standard-features/-/wallet-standard-features-1.2.0.tgz#be8b3824abf5ebcfeaa7298445bf53f76a27c935" + integrity sha512-tUd9srDLkRpe1BYg7we+c4UhRQkq+XQWswsr/L1xfGmoRDF47BPSXf4zE7ZU2GRBGvxtGt7lwJVAufQyQYhxTQ== + dependencies: + "@wallet-standard/base" "^1.0.1" + "@wallet-standard/features" "^1.0.3" + +"@solana/web3.js@^1.87.6", "@solana/web3.js@^1.93.2": + version "1.94.0" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.94.0.tgz#f662ce046f59cb294e8304beeb4d549c3ff05d73" + integrity sha512-wMiBebzu5I2fTSz623uj6VXpWFhl0d7qJKqPFK2I4IBLTNUdv+bOeA4H7OBM7Gworv7sOvB3xibRql6l61MeqA== + dependencies: + "@babel/runtime" "^7.24.7" + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + "@solana/buffer-layout" "^4.0.1" + agentkeepalive "^4.5.0" + bigint-buffer "^1.1.5" + bn.js "^5.2.1" + borsh "^0.7.0" + bs58 "^4.0.1" + buffer "6.0.3" + fast-stable-stringify "^1.0.0" + jayson "^4.1.0" + node-fetch "^2.7.0" + rpc-websockets "^9.0.2" + superstruct "^1.0.4" + "@solana/web3.js@^1.92.1": version "1.92.2" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.92.2.tgz#49760e887b31e309543668e81e1fa60f53a3e34e" @@ -1572,13 +1643,6 @@ dependencies: "@types/node" "*" -"@types/connect@^3.4.33": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - "@types/fined@*": version "1.1.3" resolved "https://registry.yarnpkg.com/@types/fined/-/fined-1.1.3.tgz#83f03e8f0a8d3673dfcafb18fce3571f6250e1bc" @@ -1696,7 +1760,6 @@ dependencies: undici-types "~5.26.4" -"@types/node@^12.12.54", "@types/node@^12.12.6": "@types/node@^12.12.54", "@types/node@^12.12.6": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" @@ -1772,6 +1835,11 @@ dependencies: "@types/node" "*" +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/ws@^7.4.4": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -1779,6 +1847,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.2.2": + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^5.16.0": version "5.47.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.0.tgz#dadb79df3b0499699b155839fd6792f16897d910" @@ -1942,6 +2017,18 @@ resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== +"@wallet-standard/base@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@wallet-standard/base/-/base-1.0.1.tgz#860dd94d47c9e3c5c43b79d91c6afdbd7a36264e" + integrity sha512-1To3ekMfzhYxe0Yhkpri+Fedq0SYcfrOfJi3vbLjMwF2qiKPjTGLwZkf2C9ftdQmxES+hmxhBzTwF4KgcOwf8w== + +"@wallet-standard/features@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@wallet-standard/features/-/features-1.0.3.tgz#c992876c5e4f7a0672f8869c4146c87e0dfe48c8" + integrity sha512-m8475I6W5LTatTZuUz5JJNK42wFRgkJTB0I9tkruMwfqBF2UN2eomkYNVf9RbrsROelCRzSFmugqjKZBFaubsA== + dependencies: + "@wallet-standard/base" "^1.0.1" + "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" @@ -1963,14 +2050,6 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1991,6 +2070,11 @@ abitype@1.0.0: resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== +abitype@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" + integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -2092,13 +2176,6 @@ agentkeepalive@^4.5.0: dependencies: humanize-ms "^1.2.1" -agentkeepalive@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== - dependencies: - humanize-ms "^1.2.1" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -2465,13 +2542,6 @@ bigint-buffer@^1.1.5: dependencies: bindings "^1.3.0" -bigint-buffer@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" - integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== - dependencies: - bindings "^1.3.0" - bigint-crypto-utils@^3.0.23: version "3.1.8" resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.1.8.tgz#e2e0f40cf45488f9d7f0e32ff84152aa73819d5d" @@ -2506,13 +2576,6 @@ bindings@^1.3.0: dependencies: file-uri-to-path "1.0.0" -bindings@^1.3.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bl@^1.0.0: version "1.2.3" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" @@ -2596,15 +2659,6 @@ borsh@^0.7.0: bs58 "^4.0.0" text-encoding-utf-8 "^1.0.2" -borsh@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" - integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== - dependencies: - bn.js "^5.2.0" - bs58 "^4.0.0" - text-encoding-utf-8 "^1.0.2" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2701,7 +2755,6 @@ browserify-sign@^4.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -bs58@^4.0.0, bs58@^4.0.1: bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -2778,14 +2831,6 @@ buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - buffer@^5.0.5, buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -3234,11 +3279,6 @@ commander@^2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^2.20.3: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -3643,11 +3683,6 @@ delay@^5.0.0: resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -3974,7 +4009,6 @@ es6-iterator@^2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-promise@^4.0.3, es6-promise@^4.2.8: es6-promise@^4.0.3, es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -3987,13 +4021,6 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== - dependencies: - es6-promise "^4.0.3" - es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" @@ -4346,6 +4373,13 @@ eth-lib@^0.1.26: ws "^3.0.0" xhr-request-promise "^0.1.2" +eth-rpc-errors@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz#6ddb6190a4bf360afda82790bb7d9d5e724f423a" + integrity sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg== + dependencies: + fast-safe-stringify "^2.0.6" + eth-sig-util@^2.5.3: version "2.5.4" resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-2.5.4.tgz#577b01fe491b6bf59b0464be09633e20c1677bc5" @@ -4693,11 +4727,6 @@ eyes@^0.1.8: resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== -eyes@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== - fast-base64-decode@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" @@ -4744,10 +4773,10 @@ fast-redact@^3.0.0, fast-redact@^3.1.1: resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== -fast-stable-stringify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" - integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== +fast-safe-stringify@^2.0.6: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== fast-stable-stringify@^1.0.0: version "1.0.0" @@ -4799,11 +4828,6 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -5759,13 +5783,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - husky@^8.0.1: version "8.0.2" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.2.tgz#5816a60db02650f1f22c8b69b928fd6bcd77a236" @@ -6264,11 +6281,6 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== -isomorphic-ws@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" - integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== - isows@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" @@ -6302,24 +6314,6 @@ jayson@^4.1.0: uuid "^8.3.2" ws "^7.4.5" -jayson@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.0.tgz#60dc946a85197317f2b1439d672a8b0a99cea2f9" - integrity sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A== - dependencies: - "@types/connect" "^3.4.33" - "@types/node" "^12.12.54" - "@types/ws" "^7.4.4" - JSONStream "^1.3.5" - commander "^2.20.3" - delay "^5.0.0" - es6-promisify "^5.0.0" - eyes "^0.1.8" - isomorphic-ws "^4.0.1" - json-stringify-safe "^5.0.1" - uuid "^8.3.2" - ws "^7.4.5" - js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -6412,7 +6406,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -7126,7 +7119,6 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.1: ms@2.1.3, ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -8256,11 +8248,6 @@ regenerator-runtime@^0.14.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -8483,6 +8470,22 @@ rpc-websockets@^7.11.1: bufferutil "^4.0.1" utf-8-validate "^5.0.2" +rpc-websockets@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.2.tgz#4c1568d00b8100f997379a363478f41f8f4b242c" + integrity sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw== + dependencies: + "@swc/helpers" "^0.5.11" + "@types/uuid" "^8.3.4" + "@types/ws" "^8.2.2" + buffer "^6.0.3" + eventemitter3 "^5.0.1" + uuid "^8.3.2" + ws "^8.5.0" + optionalDependencies: + bufferutil "^4.0.1" + utf-8-validate "^5.0.2" + run-async@^2.2.0, run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -9324,11 +9327,6 @@ text-encoding-utf-8@^1.0.2: resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== -text-encoding-utf-8@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" - integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -9870,6 +9868,20 @@ viem@^2.11.1: isows "1.0.4" ws "8.17.1" +viem@^2.16.2: + version "2.17.3" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.17.3.tgz#f15616049d8154b83e499eb5446e6d7fe6312626" + integrity sha512-FY/1uBQWfko4Esy8mU1RamvL64TLy91LZwFyQJ20E6AI3vTTEOctWfSn0pkMKa3okq4Gxs5dJE7q1hmWOQ7xcw== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.4.0" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + abitype "1.0.5" + isows "1.0.4" + ws "8.17.1" + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" @@ -10714,7 +10726,6 @@ ws@^3.0.0: safe-buffer "~5.1.0" ultron "~1.1.0" -ws@^7.4.5, ws@^7.4.6: ws@^7.4.5, ws@^7.4.6: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" @@ -10725,11 +10736,6 @@ ws@^8.5.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== -ws@^8.5.0: - version "8.17.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" - integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== - xhr-request-promise@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" From ef20b750dc6a7f0133417b1b9bd6b561ebad84b4 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 9 Jul 2024 16:02:16 +0300 Subject: [PATCH 07/29] Update deploy script and finish demo --- deployments/_deployments_log_file.json | 8 +- deployments/arbitrum.staging.json | 2 +- script/demoScripts/demoIntentFactory.ts | 80 +++++++++++++++---- .../deploy/facets/DeployIntentFactory.s.sol | 2 + 4 files changed, 73 insertions(+), 19 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index f59283a82..eef5a1e62 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -20496,11 +20496,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xd5F7C3CB19610Ec98d5b4eA991E28d6522542Bfd", + "ADDRESS": "0x6947Fd6D099d2F2E50C02C90423C3133c7D0D3B6", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-07-08 15:15:41", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c7f2cf4845c6db0e1a1e91ed41bcd0fcc1b0e141", - "SALT": "", + "TIMESTAMP": "2024-07-09 15:55:42", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000f96a44d62bd33ea4af9ed18efaae190f5924ddc8", + "SALT": "09072024", "VERIFIED": "false" } ] diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 48212d6a2..d632dd7d0 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -35,5 +35,5 @@ "DeBridgeDlnFacet": "0xE500dED7b9C9f1020870B7a6Db076Dbd892C0fea", "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "StandardizedCallFacet": "0x637Ac9AddC9C38b3F52878E11620a9060DC71d8B", - "IntentFactory": "0xd5F7C3CB19610Ec98d5b4eA991E28d6522542Bfd" + "IntentFactory": "0x6947Fd6D099d2F2E50C02C90423C3133c7D0D3B6" } \ No newline at end of file diff --git a/script/demoScripts/demoIntentFactory.ts b/script/demoScripts/demoIntentFactory.ts index 776a21c14..53a5b6184 100644 --- a/script/demoScripts/demoIntentFactory.ts +++ b/script/demoScripts/demoIntentFactory.ts @@ -5,10 +5,13 @@ import * as IntentFactory from '../../out/IntentFactory.sol/IntentFactory.json' import { Address, Hex, + SendTransactionRequest, createPublicClient, createWalletClient, + encodeFunctionData, http, keccak256, + parseAbi, parseUnits, toHex, } from 'viem' @@ -17,8 +20,13 @@ import { ChainId, getQuote } from '@lifi/sdk' const INTENT_FACTORY_ADDReSS = Deployments.IntentFactory as Address const ABI = IntentFactory.abi +const ERC20_ABI = parseAbi([ + 'function transfer(address,uint256) external', + 'function approve(address,uint256) external', +]) const DAI = '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1' const USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' +const AMOUNT_TO_SWAP = '100000000000000000' const main = defineCommand({ meta: { @@ -48,38 +56,82 @@ const main = defineCommand({ transport: http(), }) + // Initialize the intentfactory const intentFactory = { address: INTENT_FACTORY_ADDReSS, abi: ABI, } + // Get an initial quote from LIFI + let quote = await getQuote({ + fromAddress: account.address, + toAddress: account.address, + fromChain: ChainId.ARB, + toChain: ChainId.ARB, + fromToken: DAI, + toToken: USDC, + fromAmount: AMOUNT_TO_SWAP, + }) + console.log(quote) + + // Calculate the intent address + const intentData = { + intentId: keccak256(toHex(parseInt(Math.random().toString()))), + receiver: account.address, + tokenOut: USDC, + amountOutMin: quote.estimate.toAmountMin, + } const predictedIntentAddress: Address = (await publicClient.readContract({ ...intentFactory, functionName: 'getIntentAddress', - args: [ - { - intentId: keccak256(toHex(parseInt(Math.random().toString()))), - receiver: account.address, - tokenOut: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', - amountOutMin: parseUnits('10', 6), - }, - ], + args: [intentData], })) as Address console.log(predictedIntentAddress) - const quote = await getQuote({ + // Send DAI to predictedIntentAddress + let tx = await walletClient.writeContract({ + address: DAI, + abi: ERC20_ABI, + functionName: 'transfer', + args: [predictedIntentAddress, BigInt(AMOUNT_TO_SWAP)], + }) + console.log(tx) + + // Get updated quote and use intent address + quote = await getQuote({ fromAddress: predictedIntentAddress, - toAddress: account.address, + toAddress: predictedIntentAddress, fromChain: ChainId.ARB, toChain: ChainId.ARB, fromToken: DAI, toToken: USDC, - fromAmount: '10000000000000000000', + fromAmount: AMOUNT_TO_SWAP, }) - console.log(quote) - // TODO: Send DAI to predictedIntentAddress - // TODO: Execute the swap + // Deploy intent and execute the swap + const calls = [] + const approveCallData = encodeFunctionData({ + abi: ERC20_ABI, + functionName: 'approve', + args: [quote.estimate.approvalAddress as Address, BigInt(AMOUNT_TO_SWAP)], + }) + calls.push({ + to: DAI, + data: approveCallData, + value: BigInt(0), + }) + calls.push({ + to: quote.transactionRequest?.to, + data: quote.transactionRequest?.data, + value: BigInt(0), + }) + tx = await walletClient.writeContract({ + address: intentFactory.address, + abi: ABI, + functionName: 'deployAndExecuteIntent', + args: [intentData, calls], + }) + console.log(tx) }, }) diff --git a/script/deploy/facets/DeployIntentFactory.s.sol b/script/deploy/facets/DeployIntentFactory.s.sol index 77840611f..1b52c61ee 100644 --- a/script/deploy/facets/DeployIntentFactory.s.sol +++ b/script/deploy/facets/DeployIntentFactory.s.sol @@ -14,7 +14,9 @@ contract DeployScript is DeployScriptBase { public returns (IntentFactory deployed, bytes memory constructorArgs) { + vm.startBroadcast(deployerPrivateKey); implementation = new Intent(); + vm.stopBroadcast(); constructorArgs = getConstructorArgs(); deployed = IntentFactory(deploy(type(IntentFactory).creationCode)); From 85500f12928264d9ed9939b42e8d35f00f826be3 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 9 Jul 2024 16:03:09 +0300 Subject: [PATCH 08/29] Update script amount --- script/demoScripts/demoIntentFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/demoScripts/demoIntentFactory.ts b/script/demoScripts/demoIntentFactory.ts index 53a5b6184..e4a871b70 100644 --- a/script/demoScripts/demoIntentFactory.ts +++ b/script/demoScripts/demoIntentFactory.ts @@ -26,7 +26,7 @@ const ERC20_ABI = parseAbi([ ]) const DAI = '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1' const USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' -const AMOUNT_TO_SWAP = '100000000000000000' +const AMOUNT_TO_SWAP = '1000000000000000000' const main = defineCommand({ meta: { From 8d6ad62d3e0fccc16815378c80380ab7ed3db4bb Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 9 Jul 2024 16:51:19 +0300 Subject: [PATCH 09/29] refactor --- deployments/_deployments_log_file.json | 8 ++-- deployments/arbitrum.staging.json | 2 +- .../deploy/facets/DeployIntentFactory.s.sol | 19 +-------- src/Helpers/Intent.sol | 39 +++++++++++++------ src/Periphery/IntentFactory.sol | 4 +- test/solidity/Periphery/IntentFactory.t.sol | 4 +- 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index eef5a1e62..ceabcb8d8 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -20496,12 +20496,12 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x6947Fd6D099d2F2E50C02C90423C3133c7D0D3B6", + "ADDRESS": "0x6e9B8579097F45E843584595541fF805101a18aA", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-07-09 15:55:42", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000f96a44d62bd33ea4af9ed18efaae190f5924ddc8", + "TIMESTAMP": "2024-07-09 16:26:46", + "CONSTRUCTOR_ARGS": "0x", "SALT": "09072024", - "VERIFIED": "false" + "VERIFIED": "true" } ] } diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index d632dd7d0..3d1489917 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -35,5 +35,5 @@ "DeBridgeDlnFacet": "0xE500dED7b9C9f1020870B7a6Db076Dbd892C0fea", "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "StandardizedCallFacet": "0x637Ac9AddC9C38b3F52878E11620a9060DC71d8B", - "IntentFactory": "0x6947Fd6D099d2F2E50C02C90423C3133c7D0D3B6" + "IntentFactory": "0x6e9B8579097F45E843584595541fF805101a18aA" } \ No newline at end of file diff --git a/script/deploy/facets/DeployIntentFactory.s.sol b/script/deploy/facets/DeployIntentFactory.s.sol index 1b52c61ee..1790b178e 100644 --- a/script/deploy/facets/DeployIntentFactory.s.sol +++ b/script/deploy/facets/DeployIntentFactory.s.sol @@ -10,24 +10,7 @@ contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("IntentFactory") {} - function run() - public - returns (IntentFactory deployed, bytes memory constructorArgs) - { - vm.startBroadcast(deployerPrivateKey); - implementation = new Intent(); - vm.stopBroadcast(); - constructorArgs = getConstructorArgs(); - + function run() public returns (IntentFactory deployed) { deployed = IntentFactory(deploy(type(IntentFactory).creationCode)); } - - function getConstructorArgs() - internal - view - override - returns (bytes memory) - { - return abi.encode(address(implementation)); - } } diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index fbbfa9c3f..e96e7e1f3 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -23,6 +23,12 @@ contract Intent { uint256 public amountOutMin; bool public executed = false; + error Unauthorized(); + error AlreadyExecuted(); + error InvalidParams(); + error ExecutionFailed(); + error InsufficientOutputAmount(); + constructor() { implementation = address(this); } @@ -30,16 +36,16 @@ contract Intent { /// @notice Initializes the intent with the given parameters. /// @param _initData The init data. function init(IIntent.InitData calldata _initData) external { - bytes32 _salt = keccak256(abi.encode(_initData)); + salt = keccak256(abi.encode(_initData)); + factory = msg.sender; address predictedAddress = LibClone.predictDeterministicAddress( implementation, - _salt, + salt, msg.sender ); - require( - address(this) == predictedAddress, - "Intent: invalid init params" - ); + if (address(this) != predictedAddress) { + revert InvalidParams(); + } intentId = _initData.intentId; receiver = _initData.receiver; @@ -50,20 +56,26 @@ contract Intent { /// @notice Executes the intent with the given calls. /// @param calls The calls to execute. function execute(IIntent.Call[] calldata calls) external { - require(!executed, "Intent: already executed"); + if (msg.sender != factory) { + revert Unauthorized(); + } + if (executed) { + revert AlreadyExecuted(); + } executed = true; for (uint256 i = 0; i < calls.length; i++) { (bool success, ) = calls[i].to.call{ value: calls[i].value }( calls[i].data ); - require(success, "Intent: call failed"); + if (!success) { + revert ExecutionFailed(); + } } - require( - IERC20(tokenOut).balanceOf(address(this)) >= amountOutMin, - "Intent: insufficient output amount" - ); + if (IERC20(tokenOut).balanceOf(address(this)) < amountOutMin) { + revert InsufficientOutputAmount(); + } if (tokenOut == address(0)) { SafeTransferLib.safeTransferAllETH(receiver); return; @@ -74,6 +86,9 @@ contract Intent { /// @notice Withdraws all the tokens. /// @param tokens The tokens to withdraw. function withdrawAll(address[] calldata tokens) external { + if (msg.sender != factory) { + revert Unauthorized(); + } for (uint256 i = 0; i < tokens.length; i++) { if (tokens[i] == address(0)) { SafeTransferLib.safeTransferAllETH(receiver); diff --git a/src/Periphery/IntentFactory.sol b/src/Periphery/IntentFactory.sol index 46d6c5134..0c583e6a4 100644 --- a/src/Periphery/IntentFactory.sol +++ b/src/Periphery/IntentFactory.sol @@ -16,8 +16,8 @@ contract IntentFactory { /// Constructor /// - constructor(address _implementation) { - implementation = _implementation; + constructor() { + implementation = address(new Intent()); } /// External Functions /// diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index 93851d7d3..716cc8e34 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -25,8 +25,8 @@ contract IntentFactoryTest is Test { } function deploy() public returns (Intent, IntentFactory) { - address _implementation = address(new Intent()); - IntentFactory _factory = new IntentFactory(_implementation); + IntentFactory _factory = new IntentFactory(); + address _implementation = _factory.implementation(); return (Intent(_implementation), _factory); } From 424f0d6842900e1d432f1af4cb511e61cfaa009b Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 10 Jul 2024 14:35:24 +0300 Subject: [PATCH 10/29] add native test case --- test/solidity/Periphery/IntentFactory.t.sol | 50 +++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index 716cc8e34..5b4a2be60 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -94,6 +94,56 @@ contract IntentFactoryTest is Test { assertEq(tokenA.balanceOf(intentClone), 0); } + function test_can_deposit_native_and_execute_swap() public { + bytes32 intentId = keccak256("intentId"); + + // Compute the address of the intent + address intentClone = factory.getIntentAddress( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amountOutMin: 100 + }) + ); + + // Send tokens to the precomputed address + vm.prank(alice); + intentClone.call{ value: 0.1 ether }(""); + + IIntent.Call[] memory calls = new IIntent.Call[](1); + + // get swap calldata + bytes memory swapCalldata = abi.encodeWithSignature( + "swap(address,uint256,address,uint256)", + address(0), + 0.1 ether, + address(tokenB), + 100 + ); + calls[0] = IIntent.Call({ + to: address(amm), + value: 0, + data: swapCalldata + }); + + // execute the intent + factory.deployAndExecuteIntent( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amountOutMin: 100 + }), + calls + ); + + // assertions + assertEq(tokenB.balanceOf(alice), 100); + assertEq(tokenB.balanceOf(intentClone), 0); + assertEq(intentClone.balance, 0); + } + function test_can_deposit_and_withdraw_all() public { tokenA.mint(alice, 1000); bytes32 intentId = keccak256("intentId"); From 535838b904144d69652e79a23f9a0f4fa3abd1a2 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 10 Jul 2024 14:41:27 +0300 Subject: [PATCH 11/29] add native withdraw test --- test/solidity/Periphery/IntentFactory.t.sol | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index 5b4a2be60..229f89f57 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -109,7 +109,8 @@ contract IntentFactoryTest is Test { // Send tokens to the precomputed address vm.prank(alice); - intentClone.call{ value: 0.1 ether }(""); + (bool ok, ) = intentClone.call{ value: 0.1 ether }(""); + ok; IIntent.Call[] memory calls = new IIntent.Call[](1); @@ -159,9 +160,12 @@ contract IntentFactoryTest is Test { // Send tokens to the precomputed address vm.prank(alice); tokenA.transfer(intentClone, 1000); + (bool ok, ) = intentClone.call{ value: 1 ether }(""); + ok; // execute the intent - address[] memory tokens = new address[](1); + address[] memory tokens = new address[](2); tokens[0] = address(tokenA); + tokens[1] = address(0); factory.deployAndWithdrawAll( IIntent.InitData({ intentId: intentId, @@ -174,5 +178,6 @@ contract IntentFactoryTest is Test { // assertions assertEq(tokenA.balanceOf(alice), 1000); assertEq(tokenA.balanceOf(intentClone), 0); + assertEq(intentClone.balance, 0); } } From 3677160c5789383e269001adee012bd4ea3caf70 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 10 Jul 2024 15:15:33 +0300 Subject: [PATCH 12/29] add docs --- docs/IntentFactory.md | 85 +++++++++++++++++++++++++++++++++++++++++++ docs/README.md | 1 + 2 files changed, 86 insertions(+) create mode 100644 docs/IntentFactory.md diff --git a/docs/IntentFactory.md b/docs/IntentFactory.md new file mode 100644 index 000000000..216d4190a --- /dev/null +++ b/docs/IntentFactory.md @@ -0,0 +1,85 @@ +# Intent Factory + +## Description + +The intent factory allows for triggering an action for a user +in a non-custodial way. The user/on-ramp/protocol/bridge simply sends funds to +an address we compute. It can then deploy a contract to that address and +trigger the action the user intended. This is done by encoding the user's +intent into that address/contract. Fallback handling allows the user to +withdraw their funds at any time. + +## How To Use + +1. The first step to generating an intent is to calculate the intent contract's +deterministic address. You do this by providing the following: +- A random `bytes32` id +- The receiver address +- The address of the output token +- Minimum amount of token to receive + +```solidity +// Compute the address of the intent +address intentClone = factory.getIntentAddress( + IIntent.InitData({ + intentId: RANDOM_BYTES32_ID, + receiver: RECEIVER_ADDRESS, + tokenOut: TOKEN_OUT_ADDRESS, + amountOutMin: 100 * 10**TOKEN_OUT_DECIMALS + }) +); +``` + +2. Next the tokens needed to fulfill the intent need to be sent to the +pre-calculated address. (NOTE: you can send multiple tokens if you wish). +A normal use-case would be bridging tokens from one chain to the pre-calculated +address on another chain and waiting for the bridge to complete to execute +the intent. + +3. Execute the intent by passing an array of sequential calldata that will +yield the intended output amount for the receiver. For example, the first call +would approve the deposited token to an AMM. The next call would perform the +swap. Finally transfer any positive slippage or a pre-determined fee. As long +as the minimum output amount is left, the call will succeed and the remaining +output tokens will be transferred to the receiver. + +```solidity +IIntent.Call[] memory calls = new IIntent.Call[](2); + +// get approve calldata +bytes memory approveCalldata = abi.encodeWithSignature( + "approve(address,uint256)", + AMM_ADDRESS, + 1000 +); +calls[0] = IIntent.Call({ + to: TOKEN_OUT_ADDRESS, + value: 0, + data: approveCalldata +}); + +// get swap calldata +bytes memory swapCalldata = abi.encodeWithSignature( + "swap(address,uint256,address,uint256)", + TOKEN_IN_ADDRESS, + 1000 * 10**TOKEN_IN_DECIMALS, + TOKEN_OUT_ADDRESS, + 100 * 10**TOKEN_OUT_DECIMALS +); +calls[1] = IIntent.Call({ + to: AMM_ADDRESS, + value: 0, + data: swapCalldata +}); + +// execute the intent +factory.deployAndExecuteIntent( + IIntent.InitData({ + intentId: intentId, + receiver: RECEIVER_ADDRESS, + tokenOut: TOKEN_OUT_ADDRESS, + amountOutMin: 100 * 10**TOKEN_OUT_DECIMALS + }), + calls +); +``` diff --git a/docs/README.md b/docs/README.md index 55948f4ab..f33c04990 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,3 +56,4 @@ - [FeeCollector](./FeeCollector.md) - [Receiver](./Receiver.md) - [RelayerCelerIM](./RelayerCelerIM.md) +- [IntentFactory](./IntentFactory.md) From 6de49de57a34735a281ed9e122adfc9a7fefa286 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 10 Jul 2024 15:16:18 +0300 Subject: [PATCH 13/29] remove dead link --- docs/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index f33c04990..e4f9d5581 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,7 +26,6 @@ - [Optimism Bridge Facet](./OptimismBridgeFacet.md) - [Periphery Registry Facet](./PeripheryRegistryFacet.md) - [Polygon Bridge Facet](./PolygonBridgeFacet.md) -- [Ronin Bridge Facet](./RoninBridgeFacet.md) - [Squid Facet](./SquidFacet.md) - [Standardized Call Facet](./StandardizedCallFacet.md) - [Stargate Facet](./StargateFacet.md) From 2d90dbc4f7b0848c81d9696e4aae3242c04b6722 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 10 Jul 2024 15:32:14 +0300 Subject: [PATCH 14/29] Add ownership --- config/global.json | 1 + .../deploy/facets/DeployIntentFactory.s.sol | 28 +++++++++++++++++-- src/Helpers/Intent.sol | 8 ++++++ src/Periphery/IntentFactory.sol | 13 +++++++-- test/solidity/Periphery/IntentFactory.t.sol | 2 +- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/config/global.json b/config/global.json index c807d9f2c..a5eba239a 100644 --- a/config/global.json +++ b/config/global.json @@ -8,6 +8,7 @@ "withdrawWallet": "0x08647cc950813966142A416D40C382e2c5DB73bB", "lifuelRebalanceWallet": "0xC71284231A726A18ac85c94D75f9fe17A185BeAF", "deployerWallet": "0x11F1022cA6AdEF6400e5677528a80d49a069C00c", + "intentExecutorWallet": "0x11F1022cA6AdEF6400e5677528a80d49a069C00c", "approvedSigsForRefundWallet": [ { "sig": "0x0d19e519", diff --git a/script/deploy/facets/DeployIntentFactory.s.sol b/script/deploy/facets/DeployIntentFactory.s.sol index 1790b178e..0a8eb0700 100644 --- a/script/deploy/facets/DeployIntentFactory.s.sol +++ b/script/deploy/facets/DeployIntentFactory.s.sol @@ -3,14 +3,36 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; import { IntentFactory } from "lifi/Periphery/IntentFactory.sol"; -import { Intent } from "lifi/Helpers/Intent.sol"; +import { stdJson } from "forge-std/Script.sol"; contract DeployScript is DeployScriptBase { - Intent implementation; + using stdJson for string; constructor() DeployScriptBase("IntentFactory") {} - function run() public returns (IntentFactory deployed) { + function run() + public + returns (IntentFactory deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); deployed = IntentFactory(deploy(type(IntentFactory).creationCode)); } + + function getConstructorArgs() internal override returns (bytes memory) { + // get path of global config file + string memory globalConfigPath = string.concat( + root, + "/config/global.json" + ); + + // read file into json variable + string memory globalConfigJson = vm.readFile(globalConfigPath); + + // extract refundWallet address + address withdrawWalletAddress = globalConfigJson.readAddress( + ".intentExecutorWallet" + ); + + return abi.encode(withdrawWalletAddress); + } } diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index e96e7e1f3..9b478e506 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -14,6 +14,8 @@ interface IERC20 { /// @notice Intent contract that can execute arbitrary calls. /// @custom:version 1.0.0 contract Intent { + /// Storage /// + bytes32 public intentId; bytes32 public salt; address public receiver; @@ -23,16 +25,22 @@ contract Intent { uint256 public amountOutMin; bool public executed = false; + /// Errors /// + error Unauthorized(); error AlreadyExecuted(); error InvalidParams(); error ExecutionFailed(); error InsufficientOutputAmount(); + /// Constructor /// + constructor() { implementation = address(this); } + /// External Methods /// + /// @notice Initializes the intent with the given parameters. /// @param _initData The init data. function init(IIntent.InitData calldata _initData) external { diff --git a/src/Periphery/IntentFactory.sol b/src/Periphery/IntentFactory.sol index 0c583e6a4..4f904341d 100644 --- a/src/Periphery/IntentFactory.sol +++ b/src/Periphery/IntentFactory.sol @@ -4,19 +4,24 @@ pragma solidity ^0.8.17; import { LibClone } from "solady/utils/LibClone.sol"; import { IIntent } from "../Interfaces/IIntent.sol"; import { Intent } from "../Helpers/Intent.sol"; +import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; /// @title Intent Factory /// @author LI.FI (https://li.fi) /// @notice Deploys minimal proxies of "intents" that can execute arbitrary calls. /// @custom:version 1.0.0 -contract IntentFactory { +contract IntentFactory is TransferrableOwnership { /// Storage /// address public immutable implementation; + /// Errors /// + + error Unauthorized(); + /// Constructor /// - constructor() { + constructor(address _owner) TransferrableOwnership(_owner) { implementation = address(new Intent()); } @@ -29,6 +34,10 @@ contract IntentFactory { IIntent.InitData calldata _initData, IIntent.Call[] calldata _calls ) external { + if (msg.sender != owner) { + revert Unauthorized(); + } + bytes32 salt = keccak256(abi.encode(_initData)); address clone = LibClone.cloneDeterministic(implementation, salt); Intent(clone).init(_initData); diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index 229f89f57..c48c69818 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -25,7 +25,7 @@ contract IntentFactoryTest is Test { } function deploy() public returns (Intent, IntentFactory) { - IntentFactory _factory = new IntentFactory(); + IntentFactory _factory = new IntentFactory(address(this)); address _implementation = _factory.implementation(); return (Intent(_implementation), _factory); } From eab925e945e825dee0783be5e440a8d73f793305 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 17 Jul 2024 10:31:09 +0300 Subject: [PATCH 15/29] Fix typos --- script/deploy/facets/DeployIntentFactory.s.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/deploy/facets/DeployIntentFactory.s.sol b/script/deploy/facets/DeployIntentFactory.s.sol index 0a8eb0700..0094d739e 100644 --- a/script/deploy/facets/DeployIntentFactory.s.sol +++ b/script/deploy/facets/DeployIntentFactory.s.sol @@ -28,11 +28,11 @@ contract DeployScript is DeployScriptBase { // read file into json variable string memory globalConfigJson = vm.readFile(globalConfigPath); - // extract refundWallet address - address withdrawWalletAddress = globalConfigJson.readAddress( + // extract intentExecutorWallet address + address intentExecutorWalletAddress = globalConfigJson.readAddress( ".intentExecutorWallet" ); - return abi.encode(withdrawWalletAddress); + return abi.encode(intentExecutorWalletAddress); } } From c1f2d9ebf33eeeb4cc7b5edc7b6555de67fea4ff Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 17 Jul 2024 10:33:54 +0300 Subject: [PATCH 16/29] Fix typos --- script/demoScripts/demoIntentFactory.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/script/demoScripts/demoIntentFactory.ts b/script/demoScripts/demoIntentFactory.ts index e4a871b70..c4ccb53f5 100644 --- a/script/demoScripts/demoIntentFactory.ts +++ b/script/demoScripts/demoIntentFactory.ts @@ -4,21 +4,18 @@ import * as Deployments from '../../deployments/arbitrum.staging.json' import * as IntentFactory from '../../out/IntentFactory.sol/IntentFactory.json' import { Address, - Hex, - SendTransactionRequest, createPublicClient, createWalletClient, encodeFunctionData, http, keccak256, parseAbi, - parseUnits, toHex, } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { ChainId, getQuote } from '@lifi/sdk' -const INTENT_FACTORY_ADDReSS = Deployments.IntentFactory as Address +const INTENT_FACTORY_ADDRESS = Deployments.IntentFactory as Address const ABI = IntentFactory.abi const ERC20_ABI = parseAbi([ 'function transfer(address,uint256) external', @@ -58,7 +55,7 @@ const main = defineCommand({ // Initialize the intentfactory const intentFactory = { - address: INTENT_FACTORY_ADDReSS, + address: INTENT_FACTORY_ADDRESS, abi: ABI, } From 033559e98fc932436f39cec5465dd80f660e3c4c Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 17 Jul 2024 10:37:55 +0300 Subject: [PATCH 17/29] Update comment --- script/demoScripts/demoIntentFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/demoScripts/demoIntentFactory.ts b/script/demoScripts/demoIntentFactory.ts index c4ccb53f5..3532adb45 100644 --- a/script/demoScripts/demoIntentFactory.ts +++ b/script/demoScripts/demoIntentFactory.ts @@ -53,7 +53,7 @@ const main = defineCommand({ transport: http(), }) - // Initialize the intentfactory + // Setup the intentfactory ABI const intentFactory = { address: INTENT_FACTORY_ADDRESS, abi: ABI, From dba945dbb19c931951df83a32811d8242344630b Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 17 Jul 2024 10:43:50 +0300 Subject: [PATCH 18/29] Make recommended changes --- src/Helpers/Intent.sol | 67 ++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index 9b478e506..53d0eb4cd 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -16,14 +16,19 @@ interface IERC20 { contract Intent { /// Storage /// - bytes32 public intentId; - bytes32 public salt; - address public receiver; + struct IntentConfig { + bytes32 intentId; + bytes32 salt; + address receiver; + address factory; + address tokenOut; + uint256 amountOutMin; + uint256 deadline; + bool executed; + } + address public immutable implementation; - address public factory; - address public tokenOut; - uint256 public amountOutMin; - bool public executed = false; + IntentConfig public config; /// Errors /// @@ -44,33 +49,33 @@ contract Intent { /// @notice Initializes the intent with the given parameters. /// @param _initData The init data. function init(IIntent.InitData calldata _initData) external { - salt = keccak256(abi.encode(_initData)); - factory = msg.sender; + config.salt = keccak256(abi.encode(_initData)); + config.factory = msg.sender; address predictedAddress = LibClone.predictDeterministicAddress( implementation, - salt, + config.salt, msg.sender ); if (address(this) != predictedAddress) { revert InvalidParams(); } - intentId = _initData.intentId; - receiver = _initData.receiver; - tokenOut = _initData.tokenOut; - amountOutMin = _initData.amountOutMin; + config.intentId = _initData.intentId; + config.receiver = _initData.receiver; + config.tokenOut = _initData.tokenOut; + config.amountOutMin = _initData.amountOutMin; } /// @notice Executes the intent with the given calls. /// @param calls The calls to execute. function execute(IIntent.Call[] calldata calls) external { - if (msg.sender != factory) { + if (msg.sender != config.factory) { revert Unauthorized(); } - if (executed) { + if (config.executed) { revert AlreadyExecuted(); } - executed = true; + config.executed = true; for (uint256 i = 0; i < calls.length; i++) { (bool success, ) = calls[i].to.call{ value: calls[i].value }( @@ -81,28 +86,40 @@ contract Intent { } } - if (IERC20(tokenOut).balanceOf(address(this)) < amountOutMin) { + if ( + IERC20(config.tokenOut).balanceOf(address(this)) < + config.amountOutMin + ) { revert InsufficientOutputAmount(); } - if (tokenOut == address(0)) { - SafeTransferLib.safeTransferAllETH(receiver); + if (config.tokenOut == address(0)) { + SafeTransferLib.safeTransferAllETH(config.receiver); return; } - SafeTransferLib.safeTransferAll(tokenOut, receiver); + SafeTransferLib.safeTransferAll(config.tokenOut, config.receiver); } /// @notice Withdraws all the tokens. /// @param tokens The tokens to withdraw. function withdrawAll(address[] calldata tokens) external { - if (msg.sender != factory) { + if (msg.sender != config.factory) { revert Unauthorized(); } - for (uint256 i = 0; i < tokens.length; i++) { + for (uint256 i = 0; i < tokens.length; ) { if (tokens[i] == address(0)) { - SafeTransferLib.safeTransferAllETH(receiver); + SafeTransferLib.safeTransferAllETH(config.receiver); + unchecked { + ++i; + } continue; } - SafeTransferLib.safeTransferAll(tokens[i], receiver); + SafeTransferLib.safeTransferAll(tokens[i], config.receiver); + unchecked { + ++i; + } } } + + // Recieve ETH + receive() external payable {} } From bdbc6e046fa2605eec4d703a3a540404b1656866 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 17 Jul 2024 10:55:10 +0300 Subject: [PATCH 19/29] Fixes --- src/Interfaces/IIntent.sol | 1 + src/Periphery/IntentFactory.sol | 10 +++++++--- test/solidity/Periphery/IntentFactory.t.sol | 22 +++++++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/Interfaces/IIntent.sol b/src/Interfaces/IIntent.sol index 6b957d00d..3d84c4c4b 100644 --- a/src/Interfaces/IIntent.sol +++ b/src/Interfaces/IIntent.sol @@ -13,5 +13,6 @@ interface IIntent { address receiver; address tokenOut; uint256 amountOutMin; + uint256 deadline; } } diff --git a/src/Periphery/IntentFactory.sol b/src/Periphery/IntentFactory.sol index 4f904341d..6c3e0d5d9 100644 --- a/src/Periphery/IntentFactory.sol +++ b/src/Periphery/IntentFactory.sol @@ -22,7 +22,7 @@ contract IntentFactory is TransferrableOwnership { /// Constructor /// constructor(address _owner) TransferrableOwnership(_owner) { - implementation = address(new Intent()); + implementation = payable(address(new Intent())); } /// External Functions /// @@ -39,7 +39,9 @@ contract IntentFactory is TransferrableOwnership { } bytes32 salt = keccak256(abi.encode(_initData)); - address clone = LibClone.cloneDeterministic(implementation, salt); + address payable clone = payable( + LibClone.cloneDeterministic(implementation, salt) + ); Intent(clone).init(_initData); Intent(clone).execute(_calls); } @@ -52,7 +54,9 @@ contract IntentFactory is TransferrableOwnership { address[] calldata tokens ) external { bytes32 salt = keccak256(abi.encode(_initData)); - address clone = LibClone.cloneDeterministic(implementation, salt); + address payable clone = payable( + LibClone.cloneDeterministic(implementation, salt) + ); Intent(clone).init(_initData); Intent(clone).withdrawAll(tokens); } diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index c48c69818..0499e2951 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.17; import { Test, console } from "forge-std/Test.sol"; import { Intent } from "lifi/Helpers/Intent.sol"; @@ -26,7 +26,7 @@ contract IntentFactoryTest is Test { function deploy() public returns (Intent, IntentFactory) { IntentFactory _factory = new IntentFactory(address(this)); - address _implementation = _factory.implementation(); + address payable _implementation = payable(_factory.implementation()); return (Intent(_implementation), _factory); } @@ -40,7 +40,8 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amountOutMin: 100 + amountOutMin: 100, + deadline: block.timestamp }) ); @@ -82,7 +83,8 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amountOutMin: 100 + amountOutMin: 100, + deadline: block.timestamp }), calls ); @@ -103,7 +105,8 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amountOutMin: 100 + amountOutMin: 100, + deadline: block.timestamp }) ); @@ -134,7 +137,8 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amountOutMin: 100 + amountOutMin: 100, + deadline: block.timestamp }), calls ); @@ -154,7 +158,8 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amountOutMin: 100 + amountOutMin: 100, + deadline: block.timestamp }) ); // Send tokens to the precomputed address @@ -171,7 +176,8 @@ contract IntentFactoryTest is Test { intentId: intentId, receiver: alice, tokenOut: address(tokenB), - amountOutMin: 100 + amountOutMin: 100, + deadline: block.timestamp }), tokens ); From 383d86d9654a5524c57436f960389a848a6db689 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 17 Jul 2024 11:13:51 +0300 Subject: [PATCH 20/29] Handle native outputs --- src/Helpers/Intent.sol | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index 53d0eb4cd..c021dad50 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -86,17 +86,25 @@ contract Intent { } } - if ( - IERC20(config.tokenOut).balanceOf(address(this)) < - config.amountOutMin - ) { - revert InsufficientOutputAmount(); - } - if (config.tokenOut == address(0)) { + bool isNative = config.tokenOut == address(0); + + if (isNative) { + // Handle native output + if (address(this).balance < config.amountOutMin) { + revert InsufficientOutputAmount(); + } SafeTransferLib.safeTransferAllETH(config.receiver); return; + } else { + // Handle ERC20 output + if ( + IERC20(config.tokenOut).balanceOf(address(this)) < + config.amountOutMin + ) { + revert InsufficientOutputAmount(); + } + SafeTransferLib.safeTransferAll(config.tokenOut, config.receiver); } - SafeTransferLib.safeTransferAll(config.tokenOut, config.receiver); } /// @notice Withdraws all the tokens. From a35e87e8fb59fc051b8887912fad14a44664b3f0 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 17 Jul 2024 12:47:41 +0300 Subject: [PATCH 21/29] Use uint instead of bool --- src/Helpers/Intent.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index c021dad50..509fb273a 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -24,7 +24,7 @@ contract Intent { address tokenOut; uint256 amountOutMin; uint256 deadline; - bool executed; + uint8 executed; } address public immutable implementation; @@ -72,10 +72,10 @@ contract Intent { if (msg.sender != config.factory) { revert Unauthorized(); } - if (config.executed) { + if (config.executed > 0) { revert AlreadyExecuted(); } - config.executed = true; + config.executed = 1; // Set as executed for (uint256 i = 0; i < calls.length; i++) { (bool success, ) = calls[i].to.call{ value: calls[i].value }( From 08f1b0852c0cd31a7b9cff619fea85687e07385a Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 18 Jul 2024 12:59:58 +0300 Subject: [PATCH 22/29] emit events --- src/Helpers/Intent.sol | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index 509fb273a..29684641b 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -30,6 +30,14 @@ contract Intent { address public immutable implementation; IntentConfig public config; + /// Events /// + event IntentExecuted( + bytes32 indexed intentId, + address receiver, + address tokenOut, + uint256 amountOut + ); + /// Errors /// error Unauthorized(); @@ -94,16 +102,26 @@ contract Intent { revert InsufficientOutputAmount(); } SafeTransferLib.safeTransferAllETH(config.receiver); + emit IntentExecuted( + config.intentId, + config.receiver, + config.tokenOut, + address(this).balance + ); return; } else { + uint256 balance = IERC20(config.tokenOut).balanceOf(address(this)); // Handle ERC20 output - if ( - IERC20(config.tokenOut).balanceOf(address(this)) < - config.amountOutMin - ) { + if (balance < config.amountOutMin) { revert InsufficientOutputAmount(); } SafeTransferLib.safeTransferAll(config.tokenOut, config.receiver); + emit IntentExecuted( + config.intentId, + config.receiver, + config.tokenOut, + balance + ); } } From 306be6b0bbd54b185a00f17361373173f9061e50 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 19 Jul 2024 12:58:37 +0300 Subject: [PATCH 23/29] test revert when less than minAmount --- test/solidity/Periphery/IntentFactory.t.sol | 61 +++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index 0499e2951..af276c433 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -96,6 +96,67 @@ contract IntentFactoryTest is Test { assertEq(tokenA.balanceOf(intentClone), 0); } + function test_fail_when_min_amount_not_received() public { + tokenA.mint(alice, 1000); + bytes32 intentId = keccak256("intentId"); + + // Compute the address of the intent + address intentClone = factory.getIntentAddress( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amountOutMin: 100, + deadline: block.timestamp + }) + ); + + // Send tokens to the precomputed address + vm.prank(alice); + tokenA.transfer(intentClone, 1000); + + IIntent.Call[] memory calls = new IIntent.Call[](2); + + // get approve calldata + bytes memory approveCalldata = abi.encodeWithSignature( + "approve(address,uint256)", + address(amm), + 1000 + ); + calls[0] = IIntent.Call({ + to: address(tokenA), + value: 0, + data: approveCalldata + }); + + // get swap calldata + bytes memory swapCalldata = abi.encodeWithSignature( + "swap(address,uint256,address,uint256)", + address(tokenA), + 1000, + address(tokenB), + 1 + ); + calls[1] = IIntent.Call({ + to: address(amm), + value: 0, + data: swapCalldata + }); + + vm.expectRevert(); + // execute the intent + factory.deployAndExecuteIntent( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amountOutMin: 100, + deadline: block.timestamp + }), + calls + ); + } + function test_can_deposit_native_and_execute_swap() public { bytes32 intentId = keccak256("intentId"); From 5134f82c5c1ea68f9db986e93e929427a2d68dd3 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 19 Jul 2024 16:44:03 +0300 Subject: [PATCH 24/29] catch events --- test/solidity/Periphery/IntentFactory.t.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index af276c433..24cb17669 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -16,6 +16,13 @@ contract IntentFactoryTest is Test { TestToken public tokenB; address public alice; + event IntentExecuted( + bytes32 indexed intentId, + address receiver, + address tokenOut, + uint256 amountOut + ); + function setUp() public { (implementation, factory) = deploy(); amm = new TestAMM(); @@ -77,6 +84,9 @@ contract IntentFactoryTest is Test { data: swapCalldata }); + vm.expectEmit(); + emit IntentExecuted(intentId, alice, address(tokenB), 100); + // execute the intent factory.deployAndExecuteIntent( IIntent.InitData({ @@ -192,6 +202,9 @@ contract IntentFactoryTest is Test { data: swapCalldata }); + vm.expectEmit(); + emit IntentExecuted(intentId, alice, address(tokenB), 100); + // execute the intent factory.deployAndExecuteIntent( IIntent.InitData({ From ef606a8a23990d766793fbfbdb4c5894b4f53f22 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Sun, 21 Jul 2024 21:50:48 +0300 Subject: [PATCH 25/29] test reverts when trying to reexecute intent --- test/solidity/Periphery/IntentFactory.t.sol | 80 +++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index 24cb17669..615fe5ec0 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -106,6 +106,86 @@ contract IntentFactoryTest is Test { assertEq(tokenA.balanceOf(intentClone), 0); } + function test_fails_to_execute_after_executed() public { + tokenA.mint(alice, 2000); + bytes32 intentId = keccak256("intentId"); + + // Compute the address of the intent + address intentClone = factory.getIntentAddress( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amountOutMin: 100, + deadline: block.timestamp + }) + ); + + // Send tokens to the precomputed address + vm.prank(alice); + tokenA.transfer(intentClone, 1000); + + IIntent.Call[] memory calls = new IIntent.Call[](2); + + // get approve calldata + bytes memory approveCalldata = abi.encodeWithSignature( + "approve(address,uint256)", + address(amm), + 1000 + ); + calls[0] = IIntent.Call({ + to: address(tokenA), + value: 0, + data: approveCalldata + }); + + // get swap calldata + bytes memory swapCalldata = abi.encodeWithSignature( + "swap(address,uint256,address,uint256)", + address(tokenA), + 1000, + address(tokenB), + 100 + ); + calls[1] = IIntent.Call({ + to: address(amm), + value: 0, + data: swapCalldata + }); + + vm.expectEmit(); + emit IntentExecuted(intentId, alice, address(tokenB), 100); + + // execute the intent + factory.deployAndExecuteIntent( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amountOutMin: 100, + deadline: block.timestamp + }), + calls + ); + + vm.prank(alice); + tokenA.transfer(intentClone, 1000); + + vm.expectRevert(); + + // execute the intent + factory.deployAndExecuteIntent( + IIntent.InitData({ + intentId: intentId, + receiver: alice, + tokenOut: address(tokenB), + amountOutMin: 100, + deadline: block.timestamp + }), + calls + ); + } + function test_fail_when_min_amount_not_received() public { tokenA.mint(alice, 1000); bytes32 intentId = keccak256("intentId"); From e508837a1444665cdfbe017e5e603945d3512b97 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Sun, 21 Jul 2024 21:57:09 +0300 Subject: [PATCH 26/29] test can always withdraw --- src/Helpers/Intent.sol | 3 --- test/solidity/Periphery/IntentFactory.t.sol | 10 ++++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index 29684641b..361fe5c08 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -128,9 +128,6 @@ contract Intent { /// @notice Withdraws all the tokens. /// @param tokens The tokens to withdraw. function withdrawAll(address[] calldata tokens) external { - if (msg.sender != config.factory) { - revert Unauthorized(); - } for (uint256 i = 0; i < tokens.length; ) { if (tokens[i] == address(0)) { SafeTransferLib.safeTransferAllETH(config.receiver); diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index 615fe5ec0..e3d6ce0e7 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -304,7 +304,7 @@ contract IntentFactoryTest is Test { } function test_can_deposit_and_withdraw_all() public { - tokenA.mint(alice, 1000); + tokenA.mint(alice, 2000); bytes32 intentId = keccak256("intentId"); // Compute the address of the intent address intentClone = factory.getIntentAddress( @@ -335,8 +335,14 @@ contract IntentFactoryTest is Test { }), tokens ); + + vm.prank(alice); + tokenA.transfer(intentClone, 1000); + + Intent(payable(intentClone)).withdrawAll(tokens); + // assertions - assertEq(tokenA.balanceOf(alice), 1000); + assertEq(tokenA.balanceOf(alice), 2000); assertEq(tokenA.balanceOf(intentClone), 0); assertEq(intentClone.balance, 0); } From 78c16116ba3ff98c43c2036576daef4106cdd449 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 22 Jul 2024 11:01:39 +0300 Subject: [PATCH 27/29] Allow withdraw to any address --- src/Helpers/Intent.sol | 14 +++-- src/Interfaces/IIntent.sol | 1 + src/Periphery/IntentFactory.sol | 8 ++- test/solidity/Periphery/IntentFactory.t.sol | 58 +++++++++++++-------- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/Helpers/Intent.sol b/src/Helpers/Intent.sol index 361fe5c08..efde688f1 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/Intent.sol @@ -19,6 +19,7 @@ contract Intent { struct IntentConfig { bytes32 intentId; bytes32 salt; + address owner; address receiver; address factory; address tokenOut; @@ -69,6 +70,7 @@ contract Intent { } config.intentId = _initData.intentId; + config.owner = _initData.owner; config.receiver = _initData.receiver; config.tokenOut = _initData.tokenOut; config.amountOutMin = _initData.amountOutMin; @@ -127,16 +129,22 @@ contract Intent { /// @notice Withdraws all the tokens. /// @param tokens The tokens to withdraw. - function withdrawAll(address[] calldata tokens) external { + function withdrawAll( + address[] calldata tokens, + address payable receiver + ) external { + if (msg.sender != config.factory && msg.sender != config.owner) { + revert Unauthorized(); + } for (uint256 i = 0; i < tokens.length; ) { if (tokens[i] == address(0)) { - SafeTransferLib.safeTransferAllETH(config.receiver); + SafeTransferLib.safeTransferAllETH(receiver); unchecked { ++i; } continue; } - SafeTransferLib.safeTransferAll(tokens[i], config.receiver); + SafeTransferLib.safeTransferAll(tokens[i], receiver); unchecked { ++i; } diff --git a/src/Interfaces/IIntent.sol b/src/Interfaces/IIntent.sol index 3d84c4c4b..9c20f52e4 100644 --- a/src/Interfaces/IIntent.sol +++ b/src/Interfaces/IIntent.sol @@ -10,6 +10,7 @@ interface IIntent { struct InitData { bytes32 intentId; + address owner; address receiver; address tokenOut; uint256 amountOutMin; diff --git a/src/Periphery/IntentFactory.sol b/src/Periphery/IntentFactory.sol index 6c3e0d5d9..3414fcf7a 100644 --- a/src/Periphery/IntentFactory.sol +++ b/src/Periphery/IntentFactory.sol @@ -51,14 +51,18 @@ contract IntentFactory is TransferrableOwnership { /// @param tokens The tokens to withdraw. function deployAndWithdrawAll( IIntent.InitData calldata _initData, - address[] calldata tokens + address[] calldata tokens, + address payable receiver ) external { + if (msg.sender != _initData.owner) { + revert Unauthorized(); + } bytes32 salt = keccak256(abi.encode(_initData)); address payable clone = payable( LibClone.cloneDeterministic(implementation, salt) ); Intent(clone).init(_initData); - Intent(clone).withdrawAll(tokens); + Intent(clone).withdrawAll(tokens, receiver); } /// @notice Predicts the address of the intent. diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index e3d6ce0e7..a3b5a519e 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -15,6 +15,7 @@ contract IntentFactoryTest is Test { TestToken public tokenA; TestToken public tokenB; address public alice; + address public receiver; event IntentExecuted( bytes32 indexed intentId, @@ -29,6 +30,7 @@ contract IntentFactoryTest is Test { tokenA = new TestToken("TokenA", "TKNA", 18); tokenB = new TestToken("TokenB", "TKNB", 18); alice = makeAddr("alice"); + receiver = makeAddr("receiver"); } function deploy() public returns (Intent, IntentFactory) { @@ -45,7 +47,8 @@ contract IntentFactoryTest is Test { address intentClone = factory.getIntentAddress( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -85,13 +88,14 @@ contract IntentFactoryTest is Test { }); vm.expectEmit(); - emit IntentExecuted(intentId, alice, address(tokenB), 100); + emit IntentExecuted(intentId, receiver, address(tokenB), 100); // execute the intent factory.deployAndExecuteIntent( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -100,7 +104,7 @@ contract IntentFactoryTest is Test { ); // assertions - assertEq(tokenB.balanceOf(alice), 100); + assertEq(tokenB.balanceOf(receiver), 100); assertEq(tokenA.balanceOf(alice), 0); assertEq(tokenB.balanceOf(intentClone), 0); assertEq(tokenA.balanceOf(intentClone), 0); @@ -114,7 +118,8 @@ contract IntentFactoryTest is Test { address intentClone = factory.getIntentAddress( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -154,13 +159,14 @@ contract IntentFactoryTest is Test { }); vm.expectEmit(); - emit IntentExecuted(intentId, alice, address(tokenB), 100); + emit IntentExecuted(intentId, receiver, address(tokenB), 100); // execute the intent factory.deployAndExecuteIntent( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -177,7 +183,8 @@ contract IntentFactoryTest is Test { factory.deployAndExecuteIntent( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -194,7 +201,8 @@ contract IntentFactoryTest is Test { address intentClone = factory.getIntentAddress( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -238,7 +246,8 @@ contract IntentFactoryTest is Test { factory.deployAndExecuteIntent( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -254,7 +263,8 @@ contract IntentFactoryTest is Test { address intentClone = factory.getIntentAddress( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -283,13 +293,14 @@ contract IntentFactoryTest is Test { }); vm.expectEmit(); - emit IntentExecuted(intentId, alice, address(tokenB), 100); + emit IntentExecuted(intentId, receiver, address(tokenB), 100); // execute the intent factory.deployAndExecuteIntent( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp @@ -298,7 +309,7 @@ contract IntentFactoryTest is Test { ); // assertions - assertEq(tokenB.balanceOf(alice), 100); + assertEq(tokenB.balanceOf(receiver), 100); assertEq(tokenB.balanceOf(intentClone), 0); assertEq(intentClone.balance, 0); } @@ -310,36 +321,41 @@ contract IntentFactoryTest is Test { address intentClone = factory.getIntentAddress( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp }) ); // Send tokens to the precomputed address - vm.prank(alice); + vm.startPrank(alice); tokenA.transfer(intentClone, 1000); (bool ok, ) = intentClone.call{ value: 1 ether }(""); ok; - // execute the intent + // Deploy and withdraw all tokens address[] memory tokens = new address[](2); tokens[0] = address(tokenA); tokens[1] = address(0); factory.deployAndWithdrawAll( IIntent.InitData({ intentId: intentId, - receiver: alice, + owner: alice, + receiver: receiver, tokenOut: address(tokenB), amountOutMin: 100, deadline: block.timestamp }), - tokens + tokens, + payable(alice) ); - vm.prank(alice); + // Send more tokens tokenA.transfer(intentClone, 1000); - Intent(payable(intentClone)).withdrawAll(tokens); + // Withdraw again + Intent(payable(intentClone)).withdrawAll(tokens, payable(alice)); + vm.stopPrank(); // assertions assertEq(tokenA.balanceOf(alice), 2000); From cf812abfaa5401cc577009e91d4660f71660adfa Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 22 Jul 2024 11:27:01 +0300 Subject: [PATCH 28/29] Test that we can withdraw even after intent is executed --- test/solidity/Periphery/IntentFactory.t.sol | 73 +++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index a3b5a519e..b43753908 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -362,4 +362,77 @@ contract IntentFactoryTest is Test { assertEq(tokenA.balanceOf(intentClone), 0); assertEq(intentClone.balance, 0); } + + function test_can_withdraw_after_intent_is_executed() public { + tokenA.mint(alice, 2000); + bytes32 intentId = keccak256("intentId"); + + // Compute the address of the intent + address intentClone = factory.getIntentAddress( + IIntent.InitData({ + intentId: intentId, + owner: alice, + receiver: receiver, + tokenOut: address(tokenB), + amountOutMin: 100, + deadline: block.timestamp + }) + ); + + // Send tokens to the precomputed address + vm.prank(alice); + tokenA.transfer(intentClone, 1000); + + IIntent.Call[] memory calls = new IIntent.Call[](2); + + // get approve calldata + bytes memory approveCalldata = abi.encodeWithSignature( + "approve(address,uint256)", + address(amm), + 1000 + ); + calls[0] = IIntent.Call({ + to: address(tokenA), + value: 0, + data: approveCalldata + }); + + // get swap calldata + bytes memory swapCalldata = abi.encodeWithSignature( + "swap(address,uint256,address,uint256)", + address(tokenA), + 1000, + address(tokenB), + 100 + ); + calls[1] = IIntent.Call({ + to: address(amm), + value: 0, + data: swapCalldata + }); + + vm.expectEmit(); + emit IntentExecuted(intentId, receiver, address(tokenB), 100); + + // execute the intent + factory.deployAndExecuteIntent( + IIntent.InitData({ + intentId: intentId, + owner: alice, + receiver: receiver, + tokenOut: address(tokenB), + amountOutMin: 100, + deadline: block.timestamp + }), + calls + ); + + vm.startPrank(alice); + tokenA.transfer(intentClone, 1000); + address[] memory tokens = new address[](1); + tokens[0] = address(tokenA); + + Intent(payable(intentClone)).withdrawAll(tokens, payable(alice)); + vm.stopPrank(); + } } From 81870fbd7945603a2125a4b969c9767cbd564dac Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 22 Jul 2024 15:00:07 +0300 Subject: [PATCH 29/29] Update naming --- .../{Intent.sol => SwapIntentHandler.sol} | 2 +- src/Periphery/IntentFactory.sol | 12 ++++++------ test/solidity/Periphery/IntentFactory.t.sol | 18 ++++++++++++------ 3 files changed, 19 insertions(+), 13 deletions(-) rename src/Helpers/{Intent.sol => SwapIntentHandler.sol} (99%) diff --git a/src/Helpers/Intent.sol b/src/Helpers/SwapIntentHandler.sol similarity index 99% rename from src/Helpers/Intent.sol rename to src/Helpers/SwapIntentHandler.sol index efde688f1..751ed3eaf 100644 --- a/src/Helpers/Intent.sol +++ b/src/Helpers/SwapIntentHandler.sol @@ -13,7 +13,7 @@ interface IERC20 { /// @author LI.FI (https://li.fi) /// @notice Intent contract that can execute arbitrary calls. /// @custom:version 1.0.0 -contract Intent { +contract SwapIntentHandler { /// Storage /// struct IntentConfig { diff --git a/src/Periphery/IntentFactory.sol b/src/Periphery/IntentFactory.sol index 3414fcf7a..7f931fea2 100644 --- a/src/Periphery/IntentFactory.sol +++ b/src/Periphery/IntentFactory.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { LibClone } from "solady/utils/LibClone.sol"; import { IIntent } from "../Interfaces/IIntent.sol"; -import { Intent } from "../Helpers/Intent.sol"; +import { SwapIntentHandler } from "../Helpers/SwapIntentHandler.sol"; import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; /// @title Intent Factory @@ -22,7 +22,7 @@ contract IntentFactory is TransferrableOwnership { /// Constructor /// constructor(address _owner) TransferrableOwnership(_owner) { - implementation = payable(address(new Intent())); + implementation = payable(address(new SwapIntentHandler())); } /// External Functions /// @@ -42,8 +42,8 @@ contract IntentFactory is TransferrableOwnership { address payable clone = payable( LibClone.cloneDeterministic(implementation, salt) ); - Intent(clone).init(_initData); - Intent(clone).execute(_calls); + SwapIntentHandler(clone).init(_initData); + SwapIntentHandler(clone).execute(_calls); } /// @notice Deploys a new intent and withdraws all the tokens. @@ -61,8 +61,8 @@ contract IntentFactory is TransferrableOwnership { address payable clone = payable( LibClone.cloneDeterministic(implementation, salt) ); - Intent(clone).init(_initData); - Intent(clone).withdrawAll(tokens, receiver); + SwapIntentHandler(clone).init(_initData); + SwapIntentHandler(clone).withdrawAll(tokens, receiver); } /// @notice Predicts the address of the intent. diff --git a/test/solidity/Periphery/IntentFactory.t.sol b/test/solidity/Periphery/IntentFactory.t.sol index b43753908..0d3e28404 100644 --- a/test/solidity/Periphery/IntentFactory.t.sol +++ b/test/solidity/Periphery/IntentFactory.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.17; import { Test, console } from "forge-std/Test.sol"; -import { Intent } from "lifi/Helpers/Intent.sol"; +import { SwapIntentHandler } from "lifi/Helpers/SwapIntentHandler.sol"; import { IIntent } from "lifi/Interfaces/IIntent.sol"; import { IntentFactory } from "lifi/Periphery/IntentFactory.sol"; import { TestToken } from "../utils/TestToken.sol"; import { TestAMM } from "../utils/TestAMM.sol"; contract IntentFactoryTest is Test { - Intent public implementation; + SwapIntentHandler public implementation; IntentFactory public factory; TestAMM public amm; TestToken public tokenA; @@ -33,10 +33,10 @@ contract IntentFactoryTest is Test { receiver = makeAddr("receiver"); } - function deploy() public returns (Intent, IntentFactory) { + function deploy() public returns (SwapIntentHandler, IntentFactory) { IntentFactory _factory = new IntentFactory(address(this)); address payable _implementation = payable(_factory.implementation()); - return (Intent(_implementation), _factory); + return (SwapIntentHandler(_implementation), _factory); } function test_can_deposit_and_execute_swap() public { @@ -354,7 +354,10 @@ contract IntentFactoryTest is Test { tokenA.transfer(intentClone, 1000); // Withdraw again - Intent(payable(intentClone)).withdrawAll(tokens, payable(alice)); + SwapIntentHandler(payable(intentClone)).withdrawAll( + tokens, + payable(alice) + ); vm.stopPrank(); // assertions @@ -432,7 +435,10 @@ contract IntentFactoryTest is Test { address[] memory tokens = new address[](1); tokens[0] = address(tokenA); - Intent(payable(intentClone)).withdrawAll(tokens, payable(alice)); + SwapIntentHandler(payable(intentClone)).withdrawAll( + tokens, + payable(alice) + ); vm.stopPrank(); } }