Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit orders can now be set to auto-expire in the Orderbook service #23

Merged
merged 1 commit into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions golang/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*htt
err = json.Unmarshal(data, &messageMap)
if err != nil {
// Fallback to raw string if unmarshalling fails
return nil, fmt.Errorf("failed to unmarshal response body: %s", string(data))
return nil, fmt.Errorf("failed to unmarshal response body - response code: %d - raw response body: %s", resp.StatusCode, string(data))
}

// Marshal the message with indentation
formattedMessage, err := json.MarshalIndent(messageMap, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal formatted message: %v - Original error: %s", err, string(data))
return nil, fmt.Errorf("failed to marshal formatted message: %v - original error: %s", err, string(data))
}

return nil, fmt.Errorf("%s", formattedMessage)
Expand Down
129 changes: 117 additions & 12 deletions golang/client/onchain/onchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import (
"strings"
"time"

"github.com/1inch/1inch-sdk/golang/helpers"
"github.com/1inch/1inch-sdk/golang/helpers/consts/amounts"
"github.com/1inch/1inch-sdk/golang/helpers/consts/chains"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"

"github.com/1inch/1inch-sdk/golang/helpers/consts/contracts"
"github.com/1inch/1inch-sdk/golang/helpers"
"github.com/1inch/1inch-sdk/golang/helpers/consts/abis"
"github.com/1inch/1inch-sdk/golang/helpers/consts/amounts"
"github.com/1inch/1inch-sdk/golang/helpers/consts/chains"
)

const gasLimit = uint64(21000000) // TODO make sure this value more dynamic
Expand Down Expand Up @@ -143,7 +143,7 @@ func GetLegacyTx(client *ethclient.Client, nonce uint64, to string, value *big.I

// ReadContractName reads the 'name' public variable from a contract.
func ReadContractName(client *ethclient.Client, contractAddress common.Address) (string, error) {
parsedABI, err := abi.JSON(strings.NewReader(contracts.Erc20Abi)) // Make a generic version of this ABI
parsedABI, err := abi.JSON(strings.NewReader(abis.Erc20)) // Make a generic version of this ABI
if err != nil {
return "", err
}
Expand Down Expand Up @@ -172,7 +172,7 @@ func ReadContractName(client *ethclient.Client, contractAddress common.Address)

// ReadContractSymbol reads the 'symbol' public variable from a contract.
func ReadContractSymbol(client *ethclient.Client, contractAddress common.Address) (string, error) {
parsedABI, err := abi.JSON(strings.NewReader(contracts.Erc20Abi)) // Make a generic version of this ABI
parsedABI, err := abi.JSON(strings.NewReader(abis.Erc20)) // Make a generic version of this ABI
if err != nil {
return "", err
}
Expand Down Expand Up @@ -201,7 +201,7 @@ func ReadContractSymbol(client *ethclient.Client, contractAddress common.Address

// ReadContractDecimals reads the 'decimals' public variable from a contract.
func ReadContractDecimals(client *ethclient.Client, contractAddress common.Address) (uint8, error) {
parsedABI, err := abi.JSON(strings.NewReader(contracts.Erc20Abi)) // Make a generic version of this ABI
parsedABI, err := abi.JSON(strings.NewReader(abis.Erc20)) // Make a generic version of this ABI
if err != nil {
return 0, err
}
Expand Down Expand Up @@ -230,7 +230,7 @@ func ReadContractDecimals(client *ethclient.Client, contractAddress common.Addre

// ReadContractNonce reads the 'nonces' public variable from a contract.
func ReadContractNonce(client *ethclient.Client, publicAddress common.Address, contractAddress common.Address) (int64, error) {
parsedABI, err := abi.JSON(strings.NewReader(contracts.Erc20Abi)) // Make a generic version of this ABI
parsedABI, err := abi.JSON(strings.NewReader(abis.Erc20)) // Make a generic version of this ABI
if err != nil {
return -1, err
}
Expand Down Expand Up @@ -263,7 +263,7 @@ func ReadContractNonce(client *ethclient.Client, publicAddress common.Address, c

// ReadContractAllowance reads the allowance a given contract has for a wallet.
func ReadContractAllowance(client *ethclient.Client, erc20Address common.Address, publicAddress common.Address, spenderAddress common.Address) (*big.Int, error) {
parsedABI, err := abi.JSON(strings.NewReader(contracts.Erc20Abi)) // Make a generic version of this ABI
parsedABI, err := abi.JSON(strings.NewReader(abis.Erc20)) // Make a generic version of this ABI
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -297,7 +297,7 @@ func ReadContractAllowance(client *ethclient.Client, erc20Address common.Address
func GetTypeHash(client *ethclient.Client, addressAsString string) (string, error) { // Pack the call to get the PERMIT_TYPEHASH constant

// Parse the ABI
parsedABI, err := abi.JSON(strings.NewReader(contracts.Erc20Abi))
parsedABI, err := abi.JSON(strings.NewReader(abis.Erc20))
if err != nil {
return "", fmt.Errorf("failed to parse contract ABI: %v", err)
}
Expand Down Expand Up @@ -338,7 +338,7 @@ func GetTypeHash(client *ethclient.Client, addressAsString string) (string, erro

func ApproveTokenForRouter(client *ethclient.Client, nonceCache map[string]uint64, config Erc20ApprovalConfig) error {
// Parse the USDC contract ABI to get the 'Approve' function signature
parsedABI, err := abi.JSON(strings.NewReader(contracts.Erc20Abi))
parsedABI, err := abi.JSON(strings.NewReader(abis.Erc20))
if err != nil {
return fmt.Errorf("failed to parse USDC ABI: %v", err)
}
Expand All @@ -365,9 +365,114 @@ func ApproveTokenForRouter(client *ethclient.Client, nonceCache map[string]uint6
return nil
}

func GetTimestampBelowCalldata(expiration int64) ([]byte, error) {

expiration = time.Now().UnixMilli()

parsedABI, err := abi.JSON(strings.NewReader(abis.SeriesNonceManager))
if err != nil {
return nil, fmt.Errorf("failed to parse ABI: %v", err)
}

// Pack the transaction data with the method signature and parameters
data, err := parsedABI.Pack("timestampBelow", big.NewInt(expiration))
if err != nil {
return nil, fmt.Errorf("failed to pack data: %v", err)
}

return data, nil
}

func GetTimeSeriesManagerNonce(client *ethclient.Client, seriesNonceManager string, publicAddress string) (*big.Int, error) {

function := "nonce"

parsedABI, err := abi.JSON(strings.NewReader(abis.SeriesNonceManager))
if err != nil {
return nil, fmt.Errorf("failed to parse ABI: %v", err)
}

// Pack the transaction data with the method signature and parameters
data, err := parsedABI.Pack(function, big.NewInt(0), common.HexToAddress(publicAddress))
if err != nil {
return nil, fmt.Errorf("failed to pack data: %v", err)
}

address := common.HexToAddress(seriesNonceManager)

// Create the call message
msg := ethereum.CallMsg{
To: &address,
Data: data,
}

// Query the blockchain
result, err := client.CallContract(context.Background(), msg, nil)
if err != nil {
return nil, fmt.Errorf("failed to retrieve the PERMIT_TYPEHASH: %v", err)
}

// Unpack the result
var nonce *big.Int
err = parsedABI.UnpackIntoInterface(&nonce, function, result)
if err != nil {
return nil, err
}

return nonce, nil
}

func GetTimestampBelowAndNonceEqualsCalldata(expiration int64, nonce *big.Int, publicAddress string) ([]byte, error) {
var (
timeInt = new(big.Int).SetUint64(uint64(expiration))
nonceInt = new(big.Int).SetUint64(nonce.Uint64())
seriesInt = new(big.Int).SetUint64(uint64(0)) // Limit orders have a static series of 0
accountInt = new(big.Int)
)

accountInt.SetString(publicAddress[2:], 16)

timeInt.Lsh(timeInt, 216)
nonceInt.Lsh(nonceInt, 176)
seriesInt.Lsh(seriesInt, 160)

result := new(big.Int)
result.Or(result, timeInt)
result.Or(result, nonceInt)
result.Or(result, seriesInt)
result.Or(result, accountInt)

parsedABI, err := abi.JSON(strings.NewReader(abis.SeriesNonceManager))
if err != nil {
return nil, fmt.Errorf("failed to parse ABI: %v", err)
}

data, err := parsedABI.Pack("timestampBelowAndNonceEquals", result)
if err != nil {
return nil, fmt.Errorf("failed to pack data: %v", err)
}

return data, nil
}

func GetPredicateCalldata(seriesNonceManager string, getTimestampBelowAndNonceEqualsCalldata []byte) ([]byte, error) {
parsedABI, err := abi.JSON(strings.NewReader(abis.AggregationRouterV5))
if err != nil {
return nil, fmt.Errorf("failed to parse ABI: %v", err)
}

// Pack the transaction data with the method signature and parameters
data, err := parsedABI.Pack("arbitraryStaticCall", common.HexToAddress(seriesNonceManager), getTimestampBelowAndNonceEqualsCalldata)
if err != nil {
return nil, fmt.Errorf("failed to pack data: %v", err)
}

return data, nil
}

func RevokeApprovalForRouter(client *ethclient.Client, nonceCache map[string]uint64, config Erc20RevokeConfig) error {
// Parse the USDC contract ABI to get the 'Approve' function signature
parsedABI, err := abi.JSON(strings.NewReader(contracts.Erc20Abi))
parsedABI, err := abi.JSON(strings.NewReader(abis.Erc20))
if err != nil {
return fmt.Errorf("failed to parse ABI: %v", err)
}
Expand Down
18 changes: 17 additions & 1 deletion golang/client/orderbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"time"

"github.com/ethereum/go-ethereum/common"

Expand All @@ -27,6 +28,11 @@ func (s *OrderbookService) CreateOrder(ctx context.Context, params orderbook.Cre
return nil, nil, err
}

// Orders only last one minute if not specified in the request
if params.ExpireAfter == 0 {
params.ExpireAfter = time.Now().Add(time.Minute).Unix()
}

// To post an order that is open to anyone, the taker address must be the zero address
if params.Taker == "" {
params.Taker = addresses.Zero
Expand Down Expand Up @@ -77,7 +83,17 @@ func (s *OrderbookService) CreateOrder(ctx context.Context, params orderbook.Cre
}
}

order, err := orderbook.CreateLimitOrder(params)
seriesNonceManager, err := contracts.GetSeriesNonceManagerFromChainId(params.ChainId)
if err != nil {
return nil, nil, fmt.Errorf("failed to get series nonce manager address: %v", err)
}

interactions, err := orderbook.GetInteractions(ethClient, seriesNonceManager, params.ExpireAfter, params.Maker)
if err != nil {
return nil, nil, fmt.Errorf("failed to get interactions: %v", err)
}

order, err := orderbook.CreateLimitOrderMessage(params, interactions)
if err != nil {
return nil, nil, err
}
Expand Down
6 changes: 0 additions & 6 deletions golang/client/orderbook/contracts.go

This file was deleted.

63 changes: 60 additions & 3 deletions golang/client/orderbook/limitorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"fmt"
"io"
"math/big"
"os"
"strings"
"time"
Expand All @@ -23,7 +24,9 @@ type Client struct {
EthClient *ethclient.Client
}

func CreateLimitOrder(orderRequest CreateOrderParams) (*Order, error) {
func CreateLimitOrderMessage(orderRequest CreateOrderParams, interactions []string) (*Order, error) {

offsets := getOffsets(interactions)

orderData := OrderData{
MakerAsset: orderRequest.MakerAsset,
Expand All @@ -34,8 +37,8 @@ func CreateLimitOrder(orderRequest CreateOrderParams) (*Order, error) {
Maker: orderRequest.Maker,
AllowedSender: "0x0000000000000000000000000000000000000000", // TODO use this
Receiver: orderRequest.Taker,
Offsets: "0", // TODO use this
Interactions: "0x", // TODO use this
Offsets: fmt.Sprintf("%v", offsets),
Interactions: interactions[4], // The 5th entry in this slice is the predicate and is the only field needed
}

aggregationRouter, err := contracts.Get1inchRouterFromChainId(orderRequest.ChainId)
Expand Down Expand Up @@ -134,6 +137,60 @@ func CreateLimitOrder(orderRequest CreateOrderParams) (*Order, error) {
}, err
}

func GetInteractions(client *ethclient.Client, seriesNonceManager string, expiration int64, maker string) ([]string, error) {

currentNonce, err := onchain.GetTimeSeriesManagerNonce(client, seriesNonceManager, maker)
if err != nil {
return nil, err
}

timeBelowAndNonceEqualsCalldata, err := onchain.GetTimestampBelowAndNonceEqualsCalldata(expiration, currentNonce, maker)

if err != nil {
return nil, fmt.Errorf("failed to get predicate calldata: %v", err)
}

predicate, err := onchain.GetPredicateCalldata(seriesNonceManager, timeBelowAndNonceEqualsCalldata)
if err != nil {
return nil, fmt.Errorf("failed to get predicate calldata: %v", err)
}

makerAssetData := `0x`
takerAssetData := `0x`
getMakingAmount := `0x`
getTakingAmount := `0x`
permit := `0x`
preInteraction := `0x`
postInteraction := `0x`

return []string{makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, fmt.Sprintf("0x%x", predicate), permit, preInteraction, postInteraction}, nil
}

func getOffsets(interactions []string) *big.Int {
var lengthMap []int
for _, interaction := range interactions {
if interaction[:2] == "0x" {
lengthMap = append(lengthMap, len(interaction)/2-1)
} else {
lengthMap = append(lengthMap, len(interaction)/2)
}
}

cumulativeSum := 0
bytesAccumulator := big.NewInt(0)
var index uint64

for _, length := range lengthMap {
cumulativeSum += length
shiftVal := big.NewInt(int64(cumulativeSum))
shiftVal.Lsh(shiftVal, uint(32*index)) // Shift left
bytesAccumulator.Add(bytesAccumulator, shiftVal) // Add to accumulator
index++
}

return bytesAccumulator
}

func ConfirmLimitOrderWithUser(order *Order, ethClient *ethclient.Client) (bool, error) {
stdOut := helpers.StdOutPrinter{}
return confirmLimitOrderWithUser(order, ethClient, os.Stdin, stdOut)
Expand Down
Loading