Skip to content

Commit

Permalink
Added support for tenderly: create forks, apply state overrides, run …
Browse files Browse the repository at this point in the history
…simulations (#5)
  • Loading branch information
Tanz0rz authored Jan 24, 2024
1 parent 2d7872a commit 016cb1d
Show file tree
Hide file tree
Showing 9 changed files with 842 additions and 203 deletions.
3 changes: 2 additions & 1 deletion golang/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
swagger-dynamic/*.json
swagger-dynamic/*.json
/tmp
4 changes: 4 additions & 0 deletions golang/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Config struct {
DevPortalApiKey string
Web3HttpProvider string
WalletKey string
TenderlyKey string
}

func (c *Config) validate() error {
Expand Down Expand Up @@ -87,6 +88,8 @@ type Client struct {
Actions *ActionService
Swap *SwapService
Orderbook *OrderbookService

TenderlyKey string
}

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

c.common.client = c
Expand Down
188 changes: 180 additions & 8 deletions golang/client/swap_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package client

import (
"context"
"encoding/hex"
"errors"
"fmt"
"log"
"math/big"
"strings"
"time"

"github.com/1inch/1inch-sdk/golang/client/tenderly"
"github.com/1inch/1inch-sdk/golang/helpers"
"github.com/1inch/1inch-sdk/golang/helpers/consts/chains"
"github.com/1inch/1inch-sdk/golang/helpers/consts/tokens"
"github.com/ethereum/go-ethereum/common"

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

// This file provides helper functions that execute swaps onchain.

type ActionService service

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

if shouldTryPermit(s.client.ChainId, approvalType) {

}

var usePermit bool

// Currently, Permit1 swaps are only tested on Ethereum and Polygon
Expand All @@ -77,7 +79,7 @@ func (s *ActionService) SwapTokens(swapParams swap.AggregationControllerGetSwapP
if typehash == typehashes.Permit1 {
usePermit = true
} else {
log.Printf("Typehash exists, but it is not recognized: %v\n", typehash)
fmt.Printf("WARN: Typehash exists, but it is not recognized: %v\n", typehash)
}
}
}
Expand Down Expand Up @@ -143,6 +145,176 @@ func (s *ActionService) SwapTokens(swapParams swap.AggregationControllerGetSwapP
return nil
}

func shouldTryPermit(chainId int, approvalType swap.ApprovalType) bool {
return approvalType == swap.PermitIfPossible || approvalType == swap.PermitAlways
// ExecuteSwap executes a swap on the Ethereum blockchain using swap data generated by GetSwapData
func (s *SwapService) ExecuteSwap(config *swap.ExecuteSwapConfig) error {

if s.client.WalletKey == "" {
return fmt.Errorf("wallet key must be set in the client config")
}

if !config.SkipWarnings {
ok, err := swap.ConfirmExecuteSwapWithUser(config, s.client.EthClient)
if err != nil {
return fmt.Errorf("failed to confirm swap: %v", err)
}
if !ok {
return errors.New("user rejected trade")
}
}

aggregationRouter, err := contracts.Get1inchRouterFromChainId(s.client.ChainId)
if err != nil {
return fmt.Errorf("failed to get 1inch router address: %v", err)
}

if !config.IsPermitSwap {
err = s.executeSwapWithApproval(aggregationRouter, config.FromToken, config.Amount, config.TransactionData, config.SkipWarnings)
if err != nil {
return fmt.Errorf("failed to execute swap with approval: %v", err)
}
} else {
err = s.executeSwapWithPermit(config.FromToken, config.TransactionData)
if err != nil {
return fmt.Errorf("failed to execute swap with permit: %v", err)
}
}

return nil
}

func (s *SwapService) executeSwapWithApproval(spenderAddress string, fromToken string, amount string, transactionData string, skipWarnings bool) error {

var value *big.Int
var err error
var approveFirst bool
if fromToken != tokens.NativeToken {
// When swapping erc20 tokens, the value set on the transaction will be 0
value = big.NewInt(0)

allowance, err := onchain.ReadContractAllowance(s.client.EthClient, common.HexToAddress(fromToken), s.client.PublicAddress, common.HexToAddress(spenderAddress))
if err != nil {
return fmt.Errorf("failed to read allowance: %v", err)
}

amountBig, err := helpers.BigIntFromString(amount)
if err != nil {
return fmt.Errorf("failed to convert amount to big.Int: %v", err)
}
if allowance.Cmp(amountBig) <= 0 {
if !skipWarnings {
ok, err := swap.ConfirmApprovalWithUser(s.client.EthClient, s.client.PublicAddress.Hex(), fromToken)
if err != nil {
return fmt.Errorf("failed to confirm approval: %v", err)
}
if !ok {
return errors.New("user rejected approval")
}
}

approveFirst = true

// Only run the approval if a tenderly key is not present
if s.client.TenderlyKey == "" {
erc20Config := onchain.Erc20ApprovalConfig{
ChainId: s.client.ChainId,
Key: s.client.WalletKey,
Erc20Address: common.HexToAddress(fromToken),
PublicAddress: s.client.PublicAddress,
SpenderAddress: common.HexToAddress(spenderAddress),
}
err = onchain.ApproveTokenForRouter(s.client.EthClient, s.client.NonceCache, erc20Config)
if err != nil {
return fmt.Errorf("failed to approve token for router: %v", err)
}
helpers.Sleep()
}
}
} else {
// When swapping from the native token, there is no need for an approval and the amount passed in must be explicitly set
value, err = helpers.BigIntFromString(amount)
if err != nil {
return fmt.Errorf("failed to convert amount to big.Int: %v", err)
}
}

hexData, err := hex.DecodeString(transactionData[2:])
if err != nil {
return fmt.Errorf("failed to decode swap data: %v", err)
}

aggregationRouter, err := contracts.Get1inchRouterFromChainId(s.client.ChainId)
if err != nil {
return fmt.Errorf("failed to get 1inch router address: %v", err)
}

txConfig := onchain.TxConfig{
Description: "Swap",
PublicAddress: s.client.PublicAddress,
PrivateKey: s.client.WalletKey,
ChainId: big.NewInt(int64(s.client.ChainId)),
Value: value,
To: aggregationRouter,
Data: hexData,
}

if s.client.TenderlyKey != "" {
_, err := tenderly.SimulateSwap(s.client.TenderlyKey, tenderly.SwapConfig{
ChainId: s.client.ChainId,
PublicAddress: s.client.PublicAddress.Hex(),
FromToken: fromToken,
TransactionData: transactionData,
ApproveFirst: approveFirst,
Value: value.String(),
})
if err != nil {
return fmt.Errorf("failed to execute tenderly simulation: %v", err)
}
} else {
err = onchain.ExecuteTransaction(txConfig, s.client.EthClient, s.client.NonceCache)
if err != nil {
return fmt.Errorf("failed to execute transaction: %v", err)
}
}
return nil
}

func (s *SwapService) executeSwapWithPermit(fromToken string, transactionData string) error {

hexData, err := hex.DecodeString(transactionData[2:])
if err != nil {
return fmt.Errorf("failed to decode swap data: %v", err)
}

aggregationRouter, err := contracts.Get1inchRouterFromChainId(s.client.ChainId)
if err != nil {
return fmt.Errorf("failed to get 1inch router address: %v", err)
}

txConfig := onchain.TxConfig{
Description: "Swap",
PublicAddress: s.client.PublicAddress,
PrivateKey: s.client.WalletKey,
ChainId: big.NewInt(int64(s.client.ChainId)),
Value: big.NewInt(0),
To: aggregationRouter,
Data: hexData,
}
if s.client.TenderlyKey != "" {
_, err := tenderly.SimulateSwap(s.client.TenderlyKey, tenderly.SwapConfig{
ChainId: s.client.ChainId,
PublicAddress: s.client.PublicAddress.Hex(),
FromToken: fromToken,
TransactionData: transactionData,
Value: "0",
})
if err != nil {
return fmt.Errorf("failed to execute tenderly simulation: %v", err)
}
} else {
err = onchain.ExecuteTransaction(txConfig, s.client.EthClient, s.client.NonceCache)
if err != nil {
return fmt.Errorf("failed to execute transaction: %v", err)
}
}
return nil
}
64 changes: 31 additions & 33 deletions golang/client/swap_actions_e2e_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build e2e
// +build e2e

package client

import (
Expand All @@ -9,26 +6,22 @@ import (
"os"
"testing"

"github.com/1inch/1inch-sdk/golang/client/onchain"
"github.com/1inch/1inch-sdk/golang/client/swap"
"github.com/1inch/1inch-sdk/golang/helpers"
"github.com/1inch/1inch-sdk/golang/helpers/consts/chains"
"github.com/1inch/1inch-sdk/golang/helpers/consts/contracts"
"github.com/1inch/1inch-sdk/golang/helpers/consts/tokens"
"github.com/1inch/1inch-sdk/golang/helpers/consts/web3providers"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func TestSwapTokensE2E(t *testing.T) {
func TestSwapTokensTenderlyE2E(t *testing.T) {

testcases := []struct {
description string
config Config
swapParams swap.AggregationControllerGetSwapParams
removeApprovalAfter bool
approvalType swap.ApprovalType
expectedOutput string
description string
config Config
swapParams swap.AggregationControllerGetSwapParams
approvalType swap.ApprovalType
expectedOutput string
}{
{
description: "Polygon - Swap 0.01 DAI for USDC - Approval - Does not support traditional permit interface",
Expand All @@ -37,6 +30,7 @@ func TestSwapTokensE2E(t *testing.T) {
WalletKey: os.Getenv("WALLET_KEY"),
Web3HttpProvider: os.Getenv("WEB_3_HTTP_PROVIDER_URL_WITH_KEY_POLYGON"),
ChainId: chains.Polygon,
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
},
swapParams: swap.AggregationControllerGetSwapParams{
Src: tokens.PolygonDai,
Expand All @@ -45,8 +39,7 @@ func TestSwapTokensE2E(t *testing.T) {
From: os.Getenv("WALLET_ADDRESS"),
Slippage: 0.5,
},
removeApprovalAfter: true,
approvalType: swap.PermitIfPossible,
approvalType: swap.PermitIfPossible,
},
{
description: "Polygon - Swap 0.01 FRAX for USDC - Approval - Forced",
Expand All @@ -55,6 +48,7 @@ func TestSwapTokensE2E(t *testing.T) {
WalletKey: os.Getenv("WALLET_KEY"),
Web3HttpProvider: os.Getenv("WEB_3_HTTP_PROVIDER_URL_WITH_KEY_POLYGON"),
ChainId: chains.Polygon,
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
},
swapParams: swap.AggregationControllerGetSwapParams{
Src: tokens.PolygonFrax,
Expand All @@ -63,8 +57,25 @@ func TestSwapTokensE2E(t *testing.T) {
From: os.Getenv("WALLET_ADDRESS"),
Slippage: 0.5,
},
removeApprovalAfter: true,
approvalType: swap.ApprovalAlways,
approvalType: swap.ApprovalAlways,
},
{
description: "Polygon - Swap 0.01 FRAX for USDC - Permit",
config: Config{
DevPortalApiKey: os.Getenv("DEV_PORTAL_TOKEN"),
WalletKey: os.Getenv("WALLET_KEY"),
Web3HttpProvider: os.Getenv("WEB_3_HTTP_PROVIDER_URL_WITH_KEY_POLYGON"),
ChainId: chains.Polygon,
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
},
swapParams: swap.AggregationControllerGetSwapParams{
Src: tokens.PolygonFrax,
Dst: tokens.PolygonUsdc,
Amount: "10000000000000000",
From: os.Getenv("WALLET_ADDRESS"),
Slippage: 0.5,
},
approvalType: swap.PermitIfPossible,
},
{
description: "Arbitrum - Swap 0.01 USDC for DAI - Approve - Arbitrum unsuported right now",
Expand All @@ -73,6 +84,7 @@ func TestSwapTokensE2E(t *testing.T) {
WalletKey: os.Getenv("WALLET_KEY"),
Web3HttpProvider: web3providers.Arbitrum,
ChainId: chains.Arbitrum,
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
},
swapParams: swap.AggregationControllerGetSwapParams{
Src: tokens.ArbitrumUsdc,
Expand All @@ -90,6 +102,7 @@ func TestSwapTokensE2E(t *testing.T) {
WalletKey: os.Getenv("WALLET_KEY"),
Web3HttpProvider: web3providers.Arbitrum,
ChainId: chains.Arbitrum,
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
},
swapParams: swap.AggregationControllerGetSwapParams{
Src: tokens.NativeToken,
Expand All @@ -108,6 +121,7 @@ func TestSwapTokensE2E(t *testing.T) {
WalletKey: os.Getenv("WALLET_KEY"),
Web3HttpProvider: web3providers.Ethereum,
ChainId: chains.Ethereum,
TenderlyKey: os.Getenv("TENDERLY_API_KEY"),
},
swapParams: swap.AggregationControllerGetSwapParams{
Src: tokens.Ethereum1inch,
Expand Down Expand Up @@ -137,22 +151,6 @@ func TestSwapTokensE2E(t *testing.T) {
log.Fatalf("Failed to swap tokens: %v", err)
}
require.NoError(t, err)
if tc.removeApprovalAfter {

allowance, err := onchain.ReadContractAllowance(c.EthClient, common.HexToAddress(tc.swapParams.Src), common.HexToAddress(tc.swapParams.From), common.HexToAddress(contracts.AggregationRouterV5))
require.NoError(t, err)

erc20Config := onchain.Erc20RevokeConfig{
ChainId: tc.config.ChainId,
Key: tc.config.WalletKey,
Erc20Address: common.HexToAddress(tc.swapParams.Src),
PublicAddress: common.HexToAddress(tc.swapParams.From),
SpenderAddress: common.HexToAddress(contracts.AggregationRouterV5),
AllowanceDecreaseAmount: allowance,
}
err = onchain.RevokeApprovalForRouter(c.EthClient, c.NonceCache, erc20Config)
require.NoError(t, err)
}
})
}
}
Loading

0 comments on commit 016cb1d

Please sign in to comment.