Skip to content

Commit 016cb1d

Browse files
authored
Added support for tenderly: create forks, apply state overrides, run simulations (#5)
1 parent 2d7872a commit 016cb1d

File tree

9 files changed

+842
-203
lines changed

9 files changed

+842
-203
lines changed

golang/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.idea
2-
swagger-dynamic/*.json
2+
swagger-dynamic/*.json
3+
/tmp

golang/client/client.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type Config struct {
4242
DevPortalApiKey string
4343
Web3HttpProvider string
4444
WalletKey string
45+
TenderlyKey string
4546
}
4647

4748
func (c *Config) validate() error {
@@ -87,6 +88,8 @@ type Client struct {
8788
Actions *ActionService
8889
Swap *SwapService
8990
Orderbook *OrderbookService
91+
92+
TenderlyKey string
9093
}
9194

9295
// NewClient creates and initializes a new Client instance based on the provided Config.
@@ -144,6 +147,7 @@ func NewClient(config Config) (*Client, error) {
144147
PublicAddress: publicAddress,
145148
RpcUrlWithKey: config.Web3HttpProvider,
146149
NonceCache: make(map[string]uint64),
150+
TenderlyKey: config.TenderlyKey,
147151
}
148152

149153
c.common.client = c

golang/client/swap_actions.go

Lines changed: 180 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ package client
22

33
import (
44
"context"
5+
"encoding/hex"
6+
"errors"
57
"fmt"
6-
"log"
8+
"math/big"
79
"strings"
810
"time"
911

12+
"github.com/1inch/1inch-sdk/golang/client/tenderly"
1013
"github.com/1inch/1inch-sdk/golang/helpers"
1114
"github.com/1inch/1inch-sdk/golang/helpers/consts/chains"
15+
"github.com/1inch/1inch-sdk/golang/helpers/consts/tokens"
1216
"github.com/ethereum/go-ethereum/common"
1317

1418
"github.com/1inch/1inch-sdk/golang/client/onchain"
@@ -18,6 +22,8 @@ import (
1822
"github.com/1inch/1inch-sdk/golang/helpers/consts/typehashes"
1923
)
2024

25+
// This file provides helper functions that execute swaps onchain.
26+
2127
type ActionService service
2228

2329
// TODO temporarily adding a bool to the function call until config refactor
@@ -59,10 +65,6 @@ func (s *ActionService) SwapTokens(swapParams swap.AggregationControllerGetSwapP
5965
Slippage: swapParams.Slippage,
6066
}
6167

62-
if shouldTryPermit(s.client.ChainId, approvalType) {
63-
64-
}
65-
6668
var usePermit bool
6769

6870
// Currently, Permit1 swaps are only tested on Ethereum and Polygon
@@ -77,7 +79,7 @@ func (s *ActionService) SwapTokens(swapParams swap.AggregationControllerGetSwapP
7779
if typehash == typehashes.Permit1 {
7880
usePermit = true
7981
} else {
80-
log.Printf("Typehash exists, but it is not recognized: %v\n", typehash)
82+
fmt.Printf("WARN: Typehash exists, but it is not recognized: %v\n", typehash)
8183
}
8284
}
8385
}
@@ -143,6 +145,176 @@ func (s *ActionService) SwapTokens(swapParams swap.AggregationControllerGetSwapP
143145
return nil
144146
}
145147

146-
func shouldTryPermit(chainId int, approvalType swap.ApprovalType) bool {
147-
return approvalType == swap.PermitIfPossible || approvalType == swap.PermitAlways
148+
// ExecuteSwap executes a swap on the Ethereum blockchain using swap data generated by GetSwapData
149+
func (s *SwapService) ExecuteSwap(config *swap.ExecuteSwapConfig) error {
150+
151+
if s.client.WalletKey == "" {
152+
return fmt.Errorf("wallet key must be set in the client config")
153+
}
154+
155+
if !config.SkipWarnings {
156+
ok, err := swap.ConfirmExecuteSwapWithUser(config, s.client.EthClient)
157+
if err != nil {
158+
return fmt.Errorf("failed to confirm swap: %v", err)
159+
}
160+
if !ok {
161+
return errors.New("user rejected trade")
162+
}
163+
}
164+
165+
aggregationRouter, err := contracts.Get1inchRouterFromChainId(s.client.ChainId)
166+
if err != nil {
167+
return fmt.Errorf("failed to get 1inch router address: %v", err)
168+
}
169+
170+
if !config.IsPermitSwap {
171+
err = s.executeSwapWithApproval(aggregationRouter, config.FromToken, config.Amount, config.TransactionData, config.SkipWarnings)
172+
if err != nil {
173+
return fmt.Errorf("failed to execute swap with approval: %v", err)
174+
}
175+
} else {
176+
err = s.executeSwapWithPermit(config.FromToken, config.TransactionData)
177+
if err != nil {
178+
return fmt.Errorf("failed to execute swap with permit: %v", err)
179+
}
180+
}
181+
182+
return nil
183+
}
184+
185+
func (s *SwapService) executeSwapWithApproval(spenderAddress string, fromToken string, amount string, transactionData string, skipWarnings bool) error {
186+
187+
var value *big.Int
188+
var err error
189+
var approveFirst bool
190+
if fromToken != tokens.NativeToken {
191+
// When swapping erc20 tokens, the value set on the transaction will be 0
192+
value = big.NewInt(0)
193+
194+
allowance, err := onchain.ReadContractAllowance(s.client.EthClient, common.HexToAddress(fromToken), s.client.PublicAddress, common.HexToAddress(spenderAddress))
195+
if err != nil {
196+
return fmt.Errorf("failed to read allowance: %v", err)
197+
}
198+
199+
amountBig, err := helpers.BigIntFromString(amount)
200+
if err != nil {
201+
return fmt.Errorf("failed to convert amount to big.Int: %v", err)
202+
}
203+
if allowance.Cmp(amountBig) <= 0 {
204+
if !skipWarnings {
205+
ok, err := swap.ConfirmApprovalWithUser(s.client.EthClient, s.client.PublicAddress.Hex(), fromToken)
206+
if err != nil {
207+
return fmt.Errorf("failed to confirm approval: %v", err)
208+
}
209+
if !ok {
210+
return errors.New("user rejected approval")
211+
}
212+
}
213+
214+
approveFirst = true
215+
216+
// Only run the approval if a tenderly key is not present
217+
if s.client.TenderlyKey == "" {
218+
erc20Config := onchain.Erc20ApprovalConfig{
219+
ChainId: s.client.ChainId,
220+
Key: s.client.WalletKey,
221+
Erc20Address: common.HexToAddress(fromToken),
222+
PublicAddress: s.client.PublicAddress,
223+
SpenderAddress: common.HexToAddress(spenderAddress),
224+
}
225+
err = onchain.ApproveTokenForRouter(s.client.EthClient, s.client.NonceCache, erc20Config)
226+
if err != nil {
227+
return fmt.Errorf("failed to approve token for router: %v", err)
228+
}
229+
helpers.Sleep()
230+
}
231+
}
232+
} else {
233+
// When swapping from the native token, there is no need for an approval and the amount passed in must be explicitly set
234+
value, err = helpers.BigIntFromString(amount)
235+
if err != nil {
236+
return fmt.Errorf("failed to convert amount to big.Int: %v", err)
237+
}
238+
}
239+
240+
hexData, err := hex.DecodeString(transactionData[2:])
241+
if err != nil {
242+
return fmt.Errorf("failed to decode swap data: %v", err)
243+
}
244+
245+
aggregationRouter, err := contracts.Get1inchRouterFromChainId(s.client.ChainId)
246+
if err != nil {
247+
return fmt.Errorf("failed to get 1inch router address: %v", err)
248+
}
249+
250+
txConfig := onchain.TxConfig{
251+
Description: "Swap",
252+
PublicAddress: s.client.PublicAddress,
253+
PrivateKey: s.client.WalletKey,
254+
ChainId: big.NewInt(int64(s.client.ChainId)),
255+
Value: value,
256+
To: aggregationRouter,
257+
Data: hexData,
258+
}
259+
260+
if s.client.TenderlyKey != "" {
261+
_, err := tenderly.SimulateSwap(s.client.TenderlyKey, tenderly.SwapConfig{
262+
ChainId: s.client.ChainId,
263+
PublicAddress: s.client.PublicAddress.Hex(),
264+
FromToken: fromToken,
265+
TransactionData: transactionData,
266+
ApproveFirst: approveFirst,
267+
Value: value.String(),
268+
})
269+
if err != nil {
270+
return fmt.Errorf("failed to execute tenderly simulation: %v", err)
271+
}
272+
} else {
273+
err = onchain.ExecuteTransaction(txConfig, s.client.EthClient, s.client.NonceCache)
274+
if err != nil {
275+
return fmt.Errorf("failed to execute transaction: %v", err)
276+
}
277+
}
278+
return nil
279+
}
280+
281+
func (s *SwapService) executeSwapWithPermit(fromToken string, transactionData string) error {
282+
283+
hexData, err := hex.DecodeString(transactionData[2:])
284+
if err != nil {
285+
return fmt.Errorf("failed to decode swap data: %v", err)
286+
}
287+
288+
aggregationRouter, err := contracts.Get1inchRouterFromChainId(s.client.ChainId)
289+
if err != nil {
290+
return fmt.Errorf("failed to get 1inch router address: %v", err)
291+
}
292+
293+
txConfig := onchain.TxConfig{
294+
Description: "Swap",
295+
PublicAddress: s.client.PublicAddress,
296+
PrivateKey: s.client.WalletKey,
297+
ChainId: big.NewInt(int64(s.client.ChainId)),
298+
Value: big.NewInt(0),
299+
To: aggregationRouter,
300+
Data: hexData,
301+
}
302+
if s.client.TenderlyKey != "" {
303+
_, err := tenderly.SimulateSwap(s.client.TenderlyKey, tenderly.SwapConfig{
304+
ChainId: s.client.ChainId,
305+
PublicAddress: s.client.PublicAddress.Hex(),
306+
FromToken: fromToken,
307+
TransactionData: transactionData,
308+
Value: "0",
309+
})
310+
if err != nil {
311+
return fmt.Errorf("failed to execute tenderly simulation: %v", err)
312+
}
313+
} else {
314+
err = onchain.ExecuteTransaction(txConfig, s.client.EthClient, s.client.NonceCache)
315+
if err != nil {
316+
return fmt.Errorf("failed to execute transaction: %v", err)
317+
}
318+
}
319+
return nil
148320
}

golang/client/swap_actions_e2e_test.go

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
//go:build e2e
2-
// +build e2e
3-
41
package client
52

63
import (
@@ -9,26 +6,22 @@ import (
96
"os"
107
"testing"
118

12-
"github.com/1inch/1inch-sdk/golang/client/onchain"
139
"github.com/1inch/1inch-sdk/golang/client/swap"
1410
"github.com/1inch/1inch-sdk/golang/helpers"
1511
"github.com/1inch/1inch-sdk/golang/helpers/consts/chains"
16-
"github.com/1inch/1inch-sdk/golang/helpers/consts/contracts"
1712
"github.com/1inch/1inch-sdk/golang/helpers/consts/tokens"
1813
"github.com/1inch/1inch-sdk/golang/helpers/consts/web3providers"
19-
"github.com/ethereum/go-ethereum/common"
2014
"github.com/stretchr/testify/require"
2115
)
2216

23-
func TestSwapTokensE2E(t *testing.T) {
17+
func TestSwapTokensTenderlyE2E(t *testing.T) {
2418

2519
testcases := []struct {
26-
description string
27-
config Config
28-
swapParams swap.AggregationControllerGetSwapParams
29-
removeApprovalAfter bool
30-
approvalType swap.ApprovalType
31-
expectedOutput string
20+
description string
21+
config Config
22+
swapParams swap.AggregationControllerGetSwapParams
23+
approvalType swap.ApprovalType
24+
expectedOutput string
3225
}{
3326
{
3427
description: "Polygon - Swap 0.01 DAI for USDC - Approval - Does not support traditional permit interface",
@@ -37,6 +30,7 @@ func TestSwapTokensE2E(t *testing.T) {
3730
WalletKey: os.Getenv("WALLET_KEY"),
3831
Web3HttpProvider: os.Getenv("WEB_3_HTTP_PROVIDER_URL_WITH_KEY_POLYGON"),
3932
ChainId: chains.Polygon,
33+
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
4034
},
4135
swapParams: swap.AggregationControllerGetSwapParams{
4236
Src: tokens.PolygonDai,
@@ -45,8 +39,7 @@ func TestSwapTokensE2E(t *testing.T) {
4539
From: os.Getenv("WALLET_ADDRESS"),
4640
Slippage: 0.5,
4741
},
48-
removeApprovalAfter: true,
49-
approvalType: swap.PermitIfPossible,
42+
approvalType: swap.PermitIfPossible,
5043
},
5144
{
5245
description: "Polygon - Swap 0.01 FRAX for USDC - Approval - Forced",
@@ -55,6 +48,7 @@ func TestSwapTokensE2E(t *testing.T) {
5548
WalletKey: os.Getenv("WALLET_KEY"),
5649
Web3HttpProvider: os.Getenv("WEB_3_HTTP_PROVIDER_URL_WITH_KEY_POLYGON"),
5750
ChainId: chains.Polygon,
51+
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
5852
},
5953
swapParams: swap.AggregationControllerGetSwapParams{
6054
Src: tokens.PolygonFrax,
@@ -63,8 +57,25 @@ func TestSwapTokensE2E(t *testing.T) {
6357
From: os.Getenv("WALLET_ADDRESS"),
6458
Slippage: 0.5,
6559
},
66-
removeApprovalAfter: true,
67-
approvalType: swap.ApprovalAlways,
60+
approvalType: swap.ApprovalAlways,
61+
},
62+
{
63+
description: "Polygon - Swap 0.01 FRAX for USDC - Permit",
64+
config: Config{
65+
DevPortalApiKey: os.Getenv("DEV_PORTAL_TOKEN"),
66+
WalletKey: os.Getenv("WALLET_KEY"),
67+
Web3HttpProvider: os.Getenv("WEB_3_HTTP_PROVIDER_URL_WITH_KEY_POLYGON"),
68+
ChainId: chains.Polygon,
69+
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
70+
},
71+
swapParams: swap.AggregationControllerGetSwapParams{
72+
Src: tokens.PolygonFrax,
73+
Dst: tokens.PolygonUsdc,
74+
Amount: "10000000000000000",
75+
From: os.Getenv("WALLET_ADDRESS"),
76+
Slippage: 0.5,
77+
},
78+
approvalType: swap.PermitIfPossible,
6879
},
6980
{
7081
description: "Arbitrum - Swap 0.01 USDC for DAI - Approve - Arbitrum unsuported right now",
@@ -73,6 +84,7 @@ func TestSwapTokensE2E(t *testing.T) {
7384
WalletKey: os.Getenv("WALLET_KEY"),
7485
Web3HttpProvider: web3providers.Arbitrum,
7586
ChainId: chains.Arbitrum,
87+
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
7688
},
7789
swapParams: swap.AggregationControllerGetSwapParams{
7890
Src: tokens.ArbitrumUsdc,
@@ -90,6 +102,7 @@ func TestSwapTokensE2E(t *testing.T) {
90102
WalletKey: os.Getenv("WALLET_KEY"),
91103
Web3HttpProvider: web3providers.Arbitrum,
92104
ChainId: chains.Arbitrum,
105+
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
93106
},
94107
swapParams: swap.AggregationControllerGetSwapParams{
95108
Src: tokens.NativeToken,
@@ -108,6 +121,7 @@ func TestSwapTokensE2E(t *testing.T) {
108121
WalletKey: os.Getenv("WALLET_KEY"),
109122
Web3HttpProvider: web3providers.Ethereum,
110123
ChainId: chains.Ethereum,
124+
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
111125
},
112126
swapParams: swap.AggregationControllerGetSwapParams{
113127
Src: tokens.Ethereum1inch,
@@ -137,22 +151,6 @@ func TestSwapTokensE2E(t *testing.T) {
137151
log.Fatalf("Failed to swap tokens: %v", err)
138152
}
139153
require.NoError(t, err)
140-
if tc.removeApprovalAfter {
141-
142-
allowance, err := onchain.ReadContractAllowance(c.EthClient, common.HexToAddress(tc.swapParams.Src), common.HexToAddress(tc.swapParams.From), common.HexToAddress(contracts.AggregationRouterV5))
143-
require.NoError(t, err)
144-
145-
erc20Config := onchain.Erc20RevokeConfig{
146-
ChainId: tc.config.ChainId,
147-
Key: tc.config.WalletKey,
148-
Erc20Address: common.HexToAddress(tc.swapParams.Src),
149-
PublicAddress: common.HexToAddress(tc.swapParams.From),
150-
SpenderAddress: common.HexToAddress(contracts.AggregationRouterV5),
151-
AllowanceDecreaseAmount: allowance,
152-
}
153-
err = onchain.RevokeApprovalForRouter(c.EthClient, c.NonceCache, erc20Config)
154-
require.NoError(t, err)
155-
}
156154
})
157155
}
158156
}

0 commit comments

Comments
 (0)