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

feat(osmosis): Outpost types, errors, and unit tests #1921

Merged
merged 54 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f7219bf
add precompile
0xstepit Oct 20, 2023
76e2b8b
add address method to precompile
0xstepit Oct 20, 2023
70a089e
add tx template
0xstepit Oct 20, 2023
e4dc369
Merge branch 'main' into stepit/osmosis-constructor
0xstepit Oct 20, 2023
52f8cd5
fix default timeout height
0xstepit Oct 20, 2023
a51569e
fix copy pasta
0xstepit Oct 20, 2023
4cdc413
add memo constructor and parser
0xstepit Oct 20, 2023
47512a2
remove unused function
0xstepit Oct 20, 2023
0e0d896
change type to slippagePercentage and define parser
0xstepit Oct 20, 2023
ee45ff3
run make format
0xstepit Oct 20, 2023
27052ba
update gomod2nix.toml file
0xstepit Oct 20, 2023
56aa3cc
add memo validation method
0xstepit Oct 20, 2023
c8d7a2d
resolve conflict
0xstepit Oct 20, 2023
3195dfa
run make format
0xstepit Oct 20, 2023
7bf01a2
update gomod2nix.toml file
0xstepit Oct 20, 2023
80a8183
add osmosis denom as package constant
0xstepit Oct 20, 2023
5a06808
run make format
0xstepit Oct 20, 2023
d750a25
merge main
0xstepit Oct 23, 2023
7a9a9f6
fix error in string
0xstepit Oct 23, 2023
bf9cfa7
remove channel const from test
0xstepit Oct 23, 2023
2ffeefb
linter fix
0xstepit Oct 23, 2023
e3f3ab5
add tests for validate tokens
0xstepit Oct 23, 2023
9f0c4dc
refactor input validation + tests
0xstepit Oct 23, 2023
42f2b9a
make format
0xstepit Oct 23, 2023
d77fbd7
add tests for packet conversion
0xstepit Oct 23, 2023
6ac3583
better naming for packet method
0xstepit Oct 23, 2023
c1d709e
remove packet printing to stdout
0xstepit Oct 23, 2023
e28b3b6
change slice package
0xstepit Oct 23, 2023
e2af607
run make format
0xstepit Oct 23, 2023
9d386f8
fix docstring + split constants
0xstepit Oct 23, 2023
a037422
fix docstring
0xstepit Oct 23, 2023
0d78421
add tests for parser
0xstepit Oct 23, 2023
bfef81b
Merge branch 'main' into stepit/osmosis-outpost-types
0xstepit Oct 23, 2023
a57542a
fix docstring typo
0xstepit Oct 24, 2023
71cbc65
uncomment memo in packet creation
0xstepit Oct 24, 2023
f7bc7ac
add next memo in the packet tests
0xstepit Oct 24, 2023
120e71b
use max len addr from package instead of hardcoded value
0xstepit Oct 24, 2023
2dc6926
run make format
0xstepit Oct 24, 2023
adb7c91
refactor parser tests
0xstepit Oct 24, 2023
e5c119f
Merge branch 'main' into stepit/osmosis-outpost-types
Vvaradinov Oct 24, 2023
981bbee
make single validation method
0xstepit Oct 24, 2023
c900981
make format
0xstepit Oct 24, 2023
60d306e
add entry to CHANGELOG
0xstepit Oct 24, 2023
7c4d49b
rename test function
0xstepit Oct 24, 2023
5374759
remove unused function
0xstepit Oct 24, 2023
a24fd24
make memo validation inputless
0xstepit Oct 25, 2023
fd8f23b
add expected error in on failed delivery test
0xstepit Oct 25, 2023
4ce844e
Merge branch 'main' into stepit/osmosis-outpost-types
0xstepit Oct 25, 2023
530e754
remove white line markdown
0xstepit Oct 25, 2023
9c019e3
add node_modules to markdown ignore
0xstepit Oct 25, 2023
15c180d
move a validation inside memo validation
0xstepit Oct 25, 2023
8e44214
Merge branch 'main' into stepit/osmosis-outpost-types
fedekunze Oct 25, 2023
c29a75a
linter
0xstepit Oct 25, 2023
af4025a
Merge branch 'main' into stepit/osmosis-outpost-types
0xstepit Oct 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (stride-outpost) [#1931](https://github.com/evmos/evmos/pull/1931) Add Stride unit testing setup.
- (stride-outpost) [#1932](https://github.com/evmos/evmos/pull/1932) Add transaction implementation and events unit tests.
- (stride-outpost) [#1935](https://github.com/evmos/evmos/pull/1935) Refactor RedeemStake method.
- (osmosis-outpost) [#1921](https://github.com/evmos/evmos/pull/1921) Add Osmosis outpost types and errors.

### Bug Fixes

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/btcsuite/btcd/btcutil v1.1.3
github.com/cometbft/cometbft v0.37.3-0.20230920093934-46df7b597e3c
github.com/cometbft/cometbft-db v0.8.0
github.com/cosmos/btcutil v1.0.5
github.com/cosmos/cosmos-proto v1.0.0-beta.3
github.com/cosmos/cosmos-sdk v0.47.5
github.com/cosmos/go-bip39 v1.0.0
Expand Down Expand Up @@ -91,7 +92,6 @@ require (
github.com/coinbase/rosetta-sdk-go/types v1.0.0 // indirect
github.com/confio/ics23/go v0.9.0 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v0.21.0-alpha.1.0.20230904092046-df3db2d96583 // indirect
github.com/cosmos/ics23/go v0.10.0 // indirect
Expand Down
2 changes: 1 addition & 1 deletion precompiles/outposts/osmosis/IOsmosisOutpost.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ interface IOsmosisOutpost {
address input,
address output,
uint256 amount,
string calldata slippage_percentage,
uint8 slippage_percentage,
uint64 window_seconds,
string calldata receiver
) external returns (bool success);
Expand Down
4 changes: 2 additions & 2 deletions precompiles/outposts/osmosis/abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@
"type": "uint256"
},
{
"internalType": "string",
"internalType": "uint8",
"name": "slippage_percentage",
"type": "string"
"type": "uint8"
},
{
"internalType": "uint64",
Expand Down
29 changes: 29 additions & 0 deletions precompiles/outposts/osmosis/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)

package osmosis

import "fmt"

var (
// ErrEmptyReceiver is raised when the receiver used in the memo is an
// empty string.
ErrEmptyReceiver = "receiver address cannot be an empty"
// ErrEmptyOnFailedDelivery is raised when the onFailedDeliver field of the
// IBC memo is an empty string.
ErrEmptyOnFailedDelivery = "onFailedDelivery cannot be empty"
// ErrTokenPairNotFound is raised when input and output tokens are the same.
ErrInputEqualOutput = "input and output token cannot be the same"
// ErrMaxSlippagePercentage is raised when the requested slippage percentage is
// higher than a pre-defined amount.
ErrSlippagePercentage = fmt.Sprintf("slippage percentage must be: 0 < slippagePercentage <= %d", MaxSlippagePercentage)
// ErrMaxWindowSeconds is raised when the requested window seconds is
// higher than a pre-defined amount.
ErrWindowSeconds = fmt.Sprintf("window seconds must be: 0 < windowSeconds <= %d", MaxWindowSeconds)
// ErrTokenPairNotFound is raised when a token pair for a certain address
// is not found and it is required by the executing function.
ErrTokenPairNotFound = "token pair for address %s not found"
// ErrInputTokenNotSupported is raised when a the osmosis outpost receive a non supported
// input token for the swap.
ErrInputTokenNotSupported = "input not supported, supported tokens: %v" //#nosec G101 -- no hardcoded credentials here
)
4 changes: 2 additions & 2 deletions precompiles/outposts/osmosis/osmosis.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ var _ vm.PrecompiledContract = &Precompile{}
//go:embed abi.json
var f embed.FS

// / Precompile is the structure that define the Osmosis outpost precompiles extending
// / the common Precompile type.
// Precompile is the structure that define the Osmosis outpost precompile extending
// the common Precompile type.
type Precompile struct {
cmn.Precompile
// IBC
Expand Down
4 changes: 3 additions & 1 deletion precompiles/outposts/osmosis/tx.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)

// Osmosis package contains the logic of the Osmosis outpost on the Evmos chain.
// This outpost uses the ics20 precompile to relay IBC packets to the Osmosis
// chain, targeting the XCSV
package osmosis

import (
Expand Down
224 changes: 224 additions & 0 deletions precompiles/outposts/osmosis/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)

package osmosis

import (
"encoding/json"
"fmt"
"math/big"

"github.com/cosmos/cosmos-sdk/types/address"
// "golang.org/x/exp/slices"

"github.com/cosmos/btcutil/bech32"
// transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
"github.com/ethereum/go-ethereum/common"
cmn "github.com/evmos/evmos/v15/precompiles/common"
)

const (
/// MaxSlippagePercentage is the maximum slippage percentage that can be used in the
/// definition of the slippage for the swap.
MaxSlippagePercentage uint8 = 20
/// MaxWindowSeconds is the maximum number of seconds that can be used in the
/// definition of the slippage for the swap.
MaxWindowSeconds uint64 = 60
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
)

const (
// OsmosisDenom is the base denom in the Osmosis chain.
OsmosisDenom = "uosmo"
)

// Twap represents a Time-Weighted Average Price configuration.
type Twap struct {
// SlippagePercentage specifies the acceptable slippage percentage for a transaction.
SlippagePercentage uint8 `json:"slippage_percentage"`
// WindowSeconds defines the duration for which the TWAP is calculated.
WindowSeconds uint64 `json:"window_seconds"`
}

// Slippage specify how to compute the slippage of the swap. For this version of the outpost
// only the TWAP is allowed.
type Slippage struct {
Twap *Twap `json:"twap"`
}

// OsmosisSwap represents the details for a swap transaction on the Osmosis chain
// using the XCS V2 contract. This payload is one of the variant of the entry_point Execute
// in the CosmWasm contract.
//
//nolint:revive
type OsmosisSwap struct {
// OutputDenom specifies the desired output denomination for the swap.
OutputDenom string `json:"output_denom"`
// Twap represents the TWAP configuration for the swap.
Slippage *Slippage `json:"slippage"`
// Receiver is the address of the entity receiving the swapped amount.
Receiver string `json:"receiver"`
// OnFailedDelivery specifies the action to be taken in case the swap delivery fails.
// This can be "do_nothing" or the address on the Osmosis chain that can recover funds
// in case of errors.
OnFailedDelivery string `json:"on_failed_delivery"`
// NextMemo contains any additional memo information for the next operation in a PFM setting.
NextMemo string `json:"next_memo,omitempty"`
}

// Msg contains the OsmosisSwap details used in the memo relayed to the Osmosis ibc hook middleware.
type Msg struct {
// OsmosisSwap provides details for a swap transaction. It's optional and can be omitted if not provided.
OsmosisSwap *OsmosisSwap `json:"osmosis_swap"`
}

// Memo wraps the message details for the IBC packet relyaed to the Osmosis chain. This include the
// address of the smart contract that will receive the Msg.
type Memo struct {
// Contract represents the address or identifier of the contract to be called.
Contract string `json:"contract"`
// Msg contains the details of the operation to be executed on the contract.
Msg *Msg `json:"msg"`
}

// RawPacketMetadata is the raw packet metadata used to construct a JSON string.
type RawPacketMetadata struct {
// The Osmosis outpost IBC memo.
Memo *Memo `json:"memo"`
}

// CreatePacketWithMemo creates the IBC packet with the memo for the Osmosis
// outpost that can be parsed by the ibc hook middleware on the Osmosis chain.
func CreatePacketWithMemo(
outputDenom, receiver, contract string,
slippagePercentage uint8,
windowSeconds uint64,
onFailedDelivery, nextMemo string,
) *RawPacketMetadata {
return &RawPacketMetadata{
&Memo{
Contract: contract,
Msg: &Msg{
OsmosisSwap: &OsmosisSwap{
OutputDenom: outputDenom,
Slippage: &Slippage{
&Twap{
SlippagePercentage: slippagePercentage,
WindowSeconds: windowSeconds,
},
},
Receiver: receiver,
OnFailedDelivery: onFailedDelivery,
NextMemo: nextMemo,
},
},
},
}
}

// ConvertToJSON convert the RawPacketMetadata type into a JSON formatted
// string.
func (r RawPacketMetadata) String() string {
// Convert the struct to a JSON string
jsonBytes, err := json.MarshalIndent(r, "", " ")
if err != nil {
return ""
}

return string(jsonBytes)
}

// Validate performs basic validation of the IBC memo for the Osmosis outpost.
// This function assumes that memo field are parsed with ParseSwapPacketData, which
// performs data casting ensuring outputDenom cannot be an empty string.
func (m Memo) Validate() error {
osmosisSwap := m.Msg.OsmosisSwap

if osmosisSwap.OnFailedDelivery == "" {
return fmt.Errorf(ErrEmptyOnFailedDelivery)
}

// Check if account is a valid bech32 address
_, _, err := bech32.Decode(osmosisSwap.Receiver, address.MaxAddrLen)
if err != nil {
return err
}

if osmosisSwap.Slippage.Twap.SlippagePercentage == 0 || osmosisSwap.Slippage.Twap.SlippagePercentage > MaxSlippagePercentage {
return fmt.Errorf(ErrSlippagePercentage)
}

if osmosisSwap.Slippage.Twap.WindowSeconds == 0 || osmosisSwap.Slippage.Twap.WindowSeconds > MaxWindowSeconds {
return fmt.Errorf(ErrWindowSeconds)
}

return nil
}

// func tmpValidate() {
// if osmosisSwap.OutputDenom == input {
// return fmt.Errorf(ErrInputEqualOutput, input)
// }
//
// osmoIBCDenom := transfertypes.DenomTrace{
// Path: fmt.Sprintf("%s/%s", portID, channelID),
// BaseDenom: OsmosisDenom,
// }.IBCDenom()
//
// // Check that the input token is evmos or osmo.
// // This constraint will be removed in future
// validInput := []string{stakingDenom, osmoIBCDenom}
// if !slices.Contains(validInput, input) {
// return fmt.Errorf(ErrInputTokenNotSupported, validInput)
// }
// }

// ParseSwapPacketData parses the packet data for the Osmosis swap function.
func ParseSwapPacketData(args []interface{}) (
sender, input, output common.Address,
amount *big.Int,
slippagePercentage uint8,
windowSeconds uint64,
receiver string,
err error,
) {
if len(args) != 7 {
return common.Address{}, common.Address{}, common.Address{}, nil, 0, 0, "", fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 7, len(args))
}

sender, ok := args[0].(common.Address)
if !ok {
return common.Address{}, common.Address{}, common.Address{}, nil, 0, 0, "", fmt.Errorf(cmn.ErrInvalidType, "sender", common.Address{}, args[0])
}

input, ok = args[1].(common.Address)
if !ok {
return common.Address{}, common.Address{}, common.Address{}, nil, 0, 0, "", fmt.Errorf(cmn.ErrInvalidType, "input", common.Address{}, args[1])
}

output, ok = args[2].(common.Address)
if !ok {
return common.Address{}, common.Address{}, common.Address{}, nil, 0, 0, "", fmt.Errorf(cmn.ErrInvalidType, "output", common.Address{}, args[2])
}

amount, ok = args[3].(*big.Int)
if !ok {
return common.Address{}, common.Address{}, common.Address{}, nil, 0, 0, "", fmt.Errorf(cmn.ErrInvalidType, "amount", big.Int{}, args[3])
}

slippagePercentage, ok = args[4].(uint8)
if !ok {
return common.Address{}, common.Address{}, common.Address{}, nil, 0, 0, "", fmt.Errorf(cmn.ErrInvalidType, "slippagePercentage", uint8(0), args[4])
}

windowSeconds, ok = args[5].(uint64)
if !ok {
return common.Address{}, common.Address{}, common.Address{}, nil, 0, 0, "", fmt.Errorf(cmn.ErrInvalidType, "windowSeconds", uint64(0), args[5])
}

receiver, ok = args[6].(string)
if !ok {
return common.Address{}, common.Address{}, common.Address{}, nil, 0, 0, "", fmt.Errorf(cmn.ErrInvalidType, "receiver", "", args[6])
}

return sender, input, output, amount, slippagePercentage, windowSeconds, receiver, nil
}