diff --git a/lib/contracts/solvernet/bindings.go b/lib/contracts/solvernet/bindings.go new file mode 100644 index 000000000..026dd62a8 --- /dev/null +++ b/lib/contracts/solvernet/bindings.go @@ -0,0 +1,83 @@ +package solvernet + +import ( + "github.com/omni-network/omni/contracts/bindings" + "github.com/omni-network/omni/lib/errors" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" +) + +var ( + bindingsABI = mustGetABI(bindings.ISolverNetBindingsMetaData) + inputsOrderData = mustGetInputs(bindingsABI, "orderData") + inputsFillOriginData = mustGetInputs(bindingsABI, "fillOriginData") +) + +func mustGetABI(metadata *bind.MetaData) *abi.ABI { + abi, err := metadata.GetAbi() + if err != nil { + panic(err) + } + + return abi +} + +func mustGetInputs(abi *abi.ABI, name string) abi.Arguments { + method, ok := abi.Methods[name] + if !ok { + panic("method not found") + } + + return method.Inputs +} + +func ParseFillOriginData(data []byte) (bindings.ISolverNetFillOriginData, error) { + unpacked, err := inputsFillOriginData.Unpack(data) + if err != nil { + return bindings.ISolverNetFillOriginData{}, errors.Wrap(err, "unpack fill data") + } + + wrap := struct { + Data bindings.ISolverNetFillOriginData + }{} + if err := inputsFillOriginData.Copy(&wrap, unpacked); err != nil { + return bindings.ISolverNetFillOriginData{}, errors.Wrap(err, "copy fill data") + } + + return wrap.Data, nil +} + +func ParseOrderData(data []byte) (bindings.ISolverNetOrderData, error) { + unpacked, err := inputsOrderData.Unpack(data) + if err != nil { + return bindings.ISolverNetOrderData{}, errors.Wrap(err, "unpack fill data") + } + + wrap := struct { + Data bindings.ISolverNetOrderData + }{} + if err := inputsOrderData.Copy(&wrap, unpacked); err != nil { + return bindings.ISolverNetOrderData{}, errors.Wrap(err, "copy fill data") + } + + return wrap.Data, nil +} + +func PackOrderData(data bindings.ISolverNetOrderData) ([]byte, error) { + packed, err := inputsOrderData.Pack(data) + if err != nil { + return nil, errors.Wrap(err, "pack fill data") + } + + return packed, nil +} + +func PackFillOriginData(data bindings.ISolverNetFillOriginData) ([]byte, error) { + packed, err := inputsFillOriginData.Pack(data) + if err != nil { + return nil, errors.Wrap(err, "pack fill data") + } + + return packed, nil +} diff --git a/solver/app/v2/bindings_internal_test.go b/lib/contracts/solvernet/bindings_test.go similarity index 62% rename from solver/app/v2/bindings_internal_test.go rename to lib/contracts/solvernet/bindings_test.go index da830beef..f9db77ef5 100644 --- a/solver/app/v2/bindings_internal_test.go +++ b/lib/contracts/solvernet/bindings_test.go @@ -1,9 +1,12 @@ -package appv2 +package solvernet_test import ( "math/big" "testing" + "github.com/omni-network/omni/contracts/bindings" + "github.com/omni-network/omni/lib/contracts/solvernet" + fuzz "github.com/google/gofuzz" "github.com/stretchr/testify/require" ) @@ -20,13 +23,13 @@ func TestParseFillOriginData(t *testing.T) { bi.SetUint64(val) }) - var data FillOriginData + var data bindings.ISolverNetFillOriginData f.Fuzz(&data) - packed, err := inputsFillOriginData.Pack(data) + packed, err := solvernet.PackFillOriginData(data) require.NoError(t, err) - parsed, err := parseFillOriginData(packed) + parsed, err := solvernet.ParseFillOriginData(packed) require.NoError(t, err) require.Equal(t, data, parsed) diff --git a/lib/contracts/solvernet/errors.go b/lib/contracts/solvernet/errors.go new file mode 100644 index 000000000..ca10203bd --- /dev/null +++ b/lib/contracts/solvernet/errors.go @@ -0,0 +1,51 @@ +package solvernet + +import ( + "strings" + + "github.com/omni-network/omni/contracts/bindings" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" +) + +func DetectCustomError(custom error) string { + contracts := map[string]*bind.MetaData{ + "inbox": bindings.SolverNetInboxMetaData, + "outbox": bindings.SolverNetOutboxMetaData, + "mock_vault": bindings.MockVaultMetaData, + "mock_token": bindings.MockTokenMetaData, + } + + errMsg := custom.Error() + + // errors from SafeTransferLib, not present in abis + safeTranserLibErrs := map[string]string{ + "0x90b8ec18": "TransferFailed()", + "0x3e3f8f73": "ApproveFailed()", + "0x7939f424": "TransferFromFailed()", + "0xb12d13eb": "ETHTransferFailed()", + "0x54cd9435": "TotalSupplyQueryFailed()", + "0x6b836e6b": "Permit2Failed()", + "0x8757f0fd": "Permit2AmountOverflow()", + } + + for id, msg := range safeTranserLibErrs { + if strings.Contains(errMsg, id) { + return "SafeTransferLib::" + msg + } + } + + for name, contract := range contracts { + abi, err := contract.GetAbi() + if err != nil { + return "BUG" + } + for n, e := range abi.Errors { + if strings.Contains(errMsg, e.ID.Hex()[:10]) { + return name + "::" + n + } + } + } + + return "unknown" +} diff --git a/lib/contracts/solvernet/order.go b/lib/contracts/solvernet/order.go new file mode 100644 index 000000000..acead96c8 --- /dev/null +++ b/lib/contracts/solvernet/order.go @@ -0,0 +1,108 @@ +package solvernet + +import ( + "context" + "math" + "time" + + "github.com/omni-network/omni/contracts/bindings" + "github.com/omni-network/omni/lib/cast" + "github.com/omni-network/omni/lib/contracts" + "github.com/omni-network/omni/lib/errors" + "github.com/omni-network/omni/lib/ethclient/ethbackend" + "github.com/omni-network/omni/lib/netconf" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var ( + // SolverNetInbox.ORDER_DATA_TYPEHASH + // keccak256("OrderData(Call call,Deposit[] deposits)Call(uint64 chainId,bytes32 target,uint256 value,bytes data,TokenExpense[] expenses)TokenExpense(bytes32 token,bytes32 spender,uint256 amount)Deposit(bytes32 token,uint256 amount)"). + orderDataTypeHash = cast.Must32(hexutil.MustDecode("0xe5be6bd381a38cd250f9aa1a05935cbcd261fe0e77e9ef6f6d07bf3b7e5d22e2")) +) + +type OpenOpts struct { + FillDeadline time.Time +} + +func WithFillDeadline(t time.Time) func(*OpenOpts) { + return func(o *OpenOpts) { + o.FillDeadline = t + } +} + +func DefaultOpenOpts() *OpenOpts { + return &OpenOpts{ + FillDeadline: time.Now().Add(24 * time.Hour), + } +} + +// OpenOrder opens an order on chainID for user. +// user pays for the order, and must be in the backend for chainID. +func OpenOrder( + ctx context.Context, + network netconf.ID, + chainID uint64, + backends ethbackend.Backends, + user common.Address, + orderData bindings.ISolverNetOrderData, + opts ...func(*OpenOpts), +) error { + backend, err := backends.Backend(chainID) + if err != nil { + return errors.Wrap(err, "get backend") + } + + addrs, err := contracts.GetAddresses(ctx, network) + if err != nil { + return errors.Wrap(err, "get addrs") + } + + txOpts, err := backend.BindOpts(ctx, user) + if err != nil { + return errors.Wrap(err, "bind opts") + } + + contract, err := bindings.NewSolverNetInbox(addrs.SolverNetInbox, backend) + if err != nil { + return errors.Wrap(err, "bind contract") + } + + o := DefaultOpenOpts() + for _, opt := range opts { + opt(o) + } + + packed, err := PackOrderData(orderData) + if err != nil { + return errors.Wrap(err, "pack order data") + } + + // fill deadline is currently not enforced by the contract + fillDeadline := o.FillDeadline.Unix() + if fillDeadline < time.Now().Unix() { + return errors.New("fill deadline must be in the future") + } else if fillDeadline > math.MaxUint32 { + return errors.New("fill deadline too far in the future") + } + + order := bindings.IERC7683OnchainCrossChainOrder{ + //nolint:gosec // overflow is checked above + FillDeadline: uint32(fillDeadline), + OrderData: packed, + OrderDataType: orderDataTypeHash, + } + + tx, err := contract.Open(txOpts, order) + if err != nil { + return errors.Wrap(err, "open tx", "custom", DetectCustomError(err)) + } + + _, err = backend.WaitMined(ctx, tx) + if err != nil { + return errors.Wrap(err, "wait mined") + } + + return nil +} diff --git a/solver/app/v2/bindings.go b/solver/app/v2/bindings.go index a35cf780f..4ba1f0902 100644 --- a/solver/app/v2/bindings.go +++ b/solver/app/v2/bindings.go @@ -21,8 +21,7 @@ const ( ) var ( - bindingsABI = mustGetABI(bindings.ISolverNetBindingsMetaData) - inboxABI = mustGetABI(bindings.SolverNetInboxMetaData) + inboxABI = mustGetABI(bindings.SolverNetInboxMetaData) // Event log topics (common.Hash). topicOpened = mustGetEventTopic(inboxABI, "Open") @@ -31,8 +30,6 @@ var ( topicReverted = mustGetEventTopic(inboxABI, "Reverted") topicFilled = mustGetEventTopic(inboxABI, "Filled") topicClaimed = mustGetEventTopic(inboxABI, "Claimed") - - inputsFillOriginData = mustGetInputs(bindingsABI, "fillOriginData") ) // eventMeta contains metadata about an event. @@ -192,30 +189,3 @@ func mustGetEventTopic(abi *abi.ABI, name string) common.Hash { return event.ID } - -// mustGetInputs returns the inputs for the method with the given name. -func mustGetInputs(abi *abi.ABI, name string) abi.Arguments { - method, ok := abi.Methods[name] - if !ok { - panic("method not found") - } - - return method.Inputs -} - -// parseFillOriginData parses FillOriginData from packed bytes. -func parseFillOriginData(data []byte) (FillOriginData, error) { - unpacked, err := inputsFillOriginData.Unpack(data) - if err != nil { - return FillOriginData{}, errors.Wrap(err, "unpack fill data") - } - - wrap := struct { - Data FillOriginData - }{} - if err := inputsFillOriginData.Copy(&wrap, unpacked); err != nil { - return FillOriginData{}, errors.Wrap(err, "copy fill data") - } - - return wrap.Data, nil -} diff --git a/solver/app/v2/procdeps.go b/solver/app/v2/procdeps.go index 72ffd92b8..c66428e3b 100644 --- a/solver/app/v2/procdeps.go +++ b/solver/app/v2/procdeps.go @@ -3,9 +3,9 @@ package appv2 import ( "context" "math/big" - "strings" "github.com/omni-network/omni/contracts/bindings" + "github.com/omni-network/omni/lib/contracts/solvernet" "github.com/omni-network/omni/lib/errors" "github.com/omni-network/omni/lib/ethclient/ethbackend" "github.com/omni-network/omni/lib/log" @@ -135,7 +135,7 @@ func newFiller( fillerData := []byte{} // fillerData is optional ERC7683 custom filler specific data, unused in our contracts tx, err := outbox.Fill(txOpts, order.ID, order.FillOriginData, fillerData) if err != nil { - return errors.Wrap(err, "fill order", "custom", detectCustomError(err)) + return errors.Wrap(err, "fill order", "custom", solvernet.DetectCustomError(err)) } else if _, err := backend.WaitMined(ctx, tx); err != nil { return errors.Wrap(err, "wait mined") } @@ -150,29 +150,6 @@ func newFiller( } } -func detectCustomError(custom error) string { - contracts := map[string]*bind.MetaData{ - "inbox": bindings.SolveInboxMetaData, - "outbox": bindings.SolveOutboxMetaData, - "mock_vault": bindings.MockVaultMetaData, - "mock_token": bindings.MockTokenMetaData, - } - - for name, contract := range contracts { - abi, err := contract.GetAbi() - if err != nil { - return "BUG" - } - for n, e := range abi.Errors { - if strings.Contains(custom.Error(), e.ID.Hex()[:10]) { - return name + "::" + n - } - } - } - - return unknown -} - func approveOutboxSpend(ctx context.Context, output bindings.IERC7683Output, backend *ethbackend.Backend, solverAddr, outboxAddr common.Address) error { if output.Token == [32]byte{} { return errors.New("cannot approve native token") @@ -258,7 +235,7 @@ func newRejector( tx, err := inbox.Reject(txOpts, order.ID, uint8(reason)) if err != nil { - return errors.Wrap(err, "reject order", "custom", detectCustomError(err)) + return errors.Wrap(err, "reject order", "custom", solvernet.DetectCustomError(err)) } else if _, err := backend.WaitMined(ctx, tx); err != nil { return errors.Wrap(err, "wait mined") } @@ -290,7 +267,7 @@ func newAcceptor( tx, err := inbox.Accept(txOpts, order.ID) if err != nil { - return errors.Wrap(err, "accept order", "custom", detectCustomError(err)) + return errors.Wrap(err, "accept order", "custom", solvernet.DetectCustomError(err)) } else if _, err := backend.WaitMined(ctx, tx); err != nil { return errors.Wrap(err, "wait mined") } diff --git a/solver/app/v2/types.go b/solver/app/v2/types.go index 5ec69fab1..8d31df853 100644 --- a/solver/app/v2/types.go +++ b/solver/app/v2/types.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/omni-network/omni/contracts/bindings" + "github.com/omni-network/omni/lib/contracts/solvernet" "github.com/omni-network/omni/lib/errors" "github.com/ethereum/go-ethereum/common" @@ -90,5 +91,5 @@ func validateResolved(o OrderResolved) error { } func (o Order) ParsedFillOriginData() (FillOriginData, error) { - return parseFillOriginData(o.FillOriginData) + return solvernet.ParseFillOriginData(o.FillOriginData) }