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

Custom CosmWasm Integration #1135

Merged
merged 38 commits into from
Mar 31, 2022
Merged

Custom CosmWasm Integration #1135

merged 38 commits into from
Mar 31, 2022

Conversation

ethanfrey
Copy link
Contributor

Description

This follows up on the earlier CosmWasm integration work and adds custom bindings for Osmosis AMM functionality.

Contract Bindings

The other half of the work lives in https://github.com/confio/osmosis-bindings (which we are happy to transfer to the osmosis org when this is merged). If you import osmo-bindings in your contract, it will get an Osmosis-specific feature flag (so it will be rejected on upload by other chains), and gets access to some custom functionality. In particular, messages to Swap or Mint native tokens. As well as various queries on the AMM. We are currently missing queries for TWAP as that is not yet implemented, but we will add such bindings in a future PR when that feature is stable in the main branch.

These APIs are stable and the contracts will look the same even if the keepers and Go data types change (I just had to do one such fix right before merge), as this PR serves as a translation layer from contracts to the AMM. The Rust bindings comes with a sample contract, which we upload and use in a lot of CI tests in this PR to ensure the whole flow (contract -> Osmosis -> contract) is working correctly.

Go adapters

I will comment key points in the PR, but we update OsmosisApp to register custom Wasm query and message handlers. All code is stored in app/wasm, which exposes high level interfaces that are imported by the OsmosisApp.

  • message_plugin.go - Code to handle the OsmosisMsg types returned and make native calls
  • queries.go and query_plugin.go - Code to handle the OsmosisQuery type and return proper data
  • wasm.go the high level binding that can be imported by OsmosisApp to register all the handlers
  • bindings/ - Go types to match the Rust types exported by https://github.com/confio/osmosis-bindings (same JSON representation)
  • testdata/ - Compiled wasm test contract and scripts to update
  • test/ - CI tests to ensure basic contract functionality (from initial permissioned integration) as well as all the custom callback handlers.

For contributor use:

  • Targeted PR against correct branch (see CONTRIBUTING.md)
  • Updated relevant documentation (docs/) or specification (x/<module>/spec/)
  • Added a relevant changelog entry to the Unreleased section in CHANGELOG.md
  • Re-reviewed Files changed in the Github PR explorer

maurolacy and others added 30 commits March 17, 2022 14:15
* Add custom querier for osmosis wasm queries

* Move tests to own dir to avoid cyclic deps

* Rename wasm keeper to (custom) query plugin

* Use helpers to convert from sdk to wasm coins
* Add osmo_reflect contract

* Start test setup for custom queries

* Fix tests

* Complete initial query test

* Rename bindings package to bindings

* Test creating and querying 2 pools

* Rename package to wasmbindings

* Download wasm uses osmo-bindings version
* Add placeholder for message handler

* Implement SwapMsg encoder

* First table test for swapping message

* Cover basic ExactIn cases

* Test ExactOut swaps as well

* Update error message

Co-authored-by: Mauro Lacy <maurolacy@users.noreply.github.com>
* Add spot price query

* Rename keeper.go to queries.go

* Add estimate price query

* Remove needless ViewKeeper interface

* GetSpotPrice refactor

* EstimatePrice refactor

* Better EstimatePrice error message

* Add TODO for multi-hop swap price estimation

* Add SpotPrice happy path integration test

* Remove FIXMEs

* Add json field names
* Update reflect contract to v0.3.0

* Adapt to bindings v0.3.0

* Add full denom query

* Add happy path full denom integ test

* Update to v0.3.0 proper (release)
Add validation helpers
* Convert messenge encoder to message decorator

* Implement and Test mint token message

* Add TODOs for future refactoring

* Update app/wasm/message_plugin.go

Co-authored-by: Mauro Lacy <maurolacy@users.noreply.github.com>

Co-authored-by: Mauro Lacy <maurolacy@users.noreply.github.com>
* Fix: sender address format

* Add estimate price integration tests

* Remove FIXME

* Improve estimate swap rate integ tests
* Add type conversions

* Refactor EstimatePrice to use same logic as SwapMsg
* Add exact in multi-hop tests

* Tests pass multi-hop exact in

* Add failing tests for ExactOut

* FImplement ExactOut multi-hop swaps

* Remove two outdated TODO comments

* Remove obsolete comments
* Add full denom unit tests

* Add get pool state unit tests

* Add get spot price unit tests

* Add estimate price extra checks

* Add estimate price unit tests

* Add tests for zero and negative amounts

* Fix: null spot price

* Fix: Avoid panic on negative amounts

Fix rebase errors / missing null checks

* Fix rebase errors

Add negative amount checks
* Add subdenom verification

* Remove TODO
* Fix: swap tokens error handling

* Rename messenger to CustomMessenger

* Add perform swap unit tests

* Fix: perform swap null swap check

* Add PerformMint function

* Add perform mint unit tests

* Fixes: perform mint null mint check

Mint negative amount
* Fix: swap tokens error handling

* Rename messenger to CustomMessenger

* Add perform swap unit tests

* Fix: perform swap null swap check

* Add PerformMint function

* Add perform mint unit tests

* Fixes: perform mint null mint check

Mint negative amount

* Update test contract to v0.4.0

* Rename EstimatePrice binding to EstimateSwap

* Rename EstimatePrice custom query to EstimateSwap

* Adapt test to EstimatePrice to EstimateSwap renaming
* Add multi-hop swap unit test

* Adjust input amount, min output

Add slippage factor

* Add more multi-hop unit tests

* Improve estimates in multi-hop tests

* Test that self-swap is disallowed

Co-authored-by: Ethan Frey <ethanfrey@users.noreply.github.com>
@ethanfrey ethanfrey requested a review from ValarDragon March 23, 2022 20:00
@codecov-commenter
Copy link

codecov-commenter commented Mar 23, 2022

Codecov Report

Merging #1135 (6a47da2) into main (f54dfd0) will decrease coverage by 0.02%.
The diff coverage is 0.00%.

@@            Coverage Diff             @@
##             main    #1135      +/-   ##
==========================================
- Coverage   21.08%   21.06%   -0.02%     
==========================================
  Files         194      196       +2     
  Lines       25277    25301      +24     
==========================================
  Hits         5329     5329              
- Misses      18987    19011      +24     
  Partials      961      961              
Impacted Files Coverage Δ
app/wasm/bindings/query.go 0.00% <0.00%> (ø)
app/wasm/bindings/types.go 0.00% <0.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f54dfd0...6a47da2. Read the comment docs.

Copy link
Contributor Author

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments to help orient the reviewer.

@@ -367,7 +369,10 @@ func (app *OsmosisApp) InitNormalKeepers(

// The last arguments can contain custom message handlers, and custom query handlers,
// if we want to allow any custom callbacks
supportedFeatures := "iterator,staking,stargate"
supportedFeatures := "iterator,staking,stargate,osmosis"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We add the extra feature here, so contract including osmo-bindings are supported here.

supportedFeatures := "iterator,staking,stargate"
supportedFeatures := "iterator,staking,stargate,osmosis"

wasmOpts = append(owasm.RegisterCustomPlugins(app.GAMMKeeper, app.BankKeeper), wasmOpts...)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here we register the custom plugins to handle those messages


var _ wasmkeeper.Messenger = (*CustomMessenger)(nil)

func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the entry point to handle messages.
If you add another variant to OsmosisMsg, you can add the handler here.

PoolId: swap.First.PoolId,
TokenInDenom: swap.First.DenomIn,
}}
output := swap.First.DenomOut
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we simplify the Rust API such that it always flows from first to last (even if we set ExactOut), and then convert to the different Go API here. This shows how we can easily adapt to new APIs as long as the same basic functionality is exposed.

}

// GetFullDenom is a function, not method, so the message_plugin can use it
func GetFullDenom(contract string, subDenom string) (string, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we build the custom denom if the contract mints a coin.
I assume this is enough, but if you need metadata registration or other such, please let us know.

RegenPool uint64
}

func TestSwapMsg(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the most interesting test for me.
Full stack table test where a caller requests the contract to perform a swap (while sending tokens to it), and then checking the result of the swap (balance remaining in the contract).

expectErr: true,
},

// FIXME: this panics in GAMM module !?! hits a known TODO
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hit this two times with ExactOut. It seems that if you trade from A -> B where 1 A is worth > 2 B then the ExactOut swaps will all fail.

When I reverse it such that the output is the more valuable currency, then ExactOut passes.

return &wasmbindings.SwapMsg{
First: wasmbindings.Swap{
PoolId: state.AtomPool,
DenomIn: "uosmo",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like here, OSMO -> ATOM works with ExactOut.

The above is a bug in the GAMM code (with existing TODO). I think it would be nice to fix, but that is out of scope of this PR

},
},

"exact in: 2 step multi-hop": {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Showing multi-hops work properly from contracts.

sdk.NewInt64Coin("uatom", 1999999),
},
},
"exact out: 2 step multi-hop": {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even when setting ExactOut (where we mangle the args to call into x/gamm)

Copy link
Member

@ValarDragon ValarDragon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work on this! Will start reviewing in depth tomorrow / learning more about how these bindings work

app/wasm/testdata/version.txt Outdated Show resolved Hide resolved
Comment on lines +5 to +8
type PoolAssets struct {
Assets []sdk.Coin
Shares sdk.Coin
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As part of the refactor, we're trying to convert a lot of instances of PoolAssets in the code / other modules, to be PoolLiquidity. We should probably revisit this wasm binding pre-release

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can gladly rename everything. Changing the type I easy and this is only used when querying the gamm, so please do suggest new names.

We use the Rust definitions as the canonical source for all types that hit the contract boundary, and this is more permanent (breaking API changes), so maybe you can double-check we use the proper names there first. Types that are only used within this package can easily be renamed by anyone

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Will review those in more depth then

@ethanfrey
Copy link
Contributor Author

This is only failing linter, but not sure how to run that locally (and there is no clear error message in the CI).

Any tips?

@ValarDragon
Copy link
Member

ValarDragon commented Mar 29, 2022

So our original thinking re: native token minting was to make a "tokenfactory" module #584 / #1029 . Would it be possible / a blocker to comment out or otherwise disable the Mint tokens API for now, and re-enable that once the token factory gets on mainnet?

The goal right now is to get a mainnet upgrade out around April 8th / applied on chain week of April 15th. So we likely can't get token factory in on that time frame. Should we aim to get osmo bindings in for swaps, just without minting tokens?

Related: how careful do we need to be around backwards compatibility? (Are the bindings versioned)

@ValarDragon
Copy link
Member

Re linting: make lint lets you run it locally. You may need to merge main into the branch, since the linter had some updates in the last week.

The error here appears to be trailing newlines, so I think make format may even fix that for you

@maurolacy
Copy link
Contributor

maurolacy commented Mar 29, 2022

So our original thinking re: native token minting was to make a "tokenfactory" module #584 / #1029 . Would it be possible / a blocker to comment out or otherwise disable the Mint tokens API for now, and re-enable that once the token factory gets on mainnet?

Sure.

The goal right now is to get a mainnet upgrade out around April 8th / applied on chain week of April 15th. So we likely can't get token factory in on that time frame. Should we aim to get osmo bindings in for swaps, just without minting tokens?

Related: how careful do we need to be around backwards compatibility? (Are the bindings versioned)

The bindings are versioned. That's what the version.txt file in app/wasm/testdata is for. Every time the bindings are (breaking) changed, a new version of osmosis would need to be released, with the integration / support for the changes.
There can be some non-breaking changes, basically depending on the json support of optional / omitted fields. But that will not be the case in general.

@ethanfrey
Copy link
Contributor Author

@ValarDragon

So our original thinking re: native token minting was to make a "tokenfactory" module #584 / #1029 . Would it be possible / a blocker to comment out or otherwise disable the Mint tokens API for now, and re-enable that once the token factory gets on mainnet?

This is commented out. Since the commits will be squashed and this will not be in the git history, I left this in as comments. When the token factory is added, we can add osmosis-v2 bindings that contain this in addition (and all v1 functionality)

// FullDenom *FullDenom `json:"full_denom,omitempty"`
/// For a given pool ID, list all tokens traded on it with current liquidity (spot).
/// As well as the total number of LP shares and their denom.
PoolState *PoolState `json:"pool_state,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename these types to have a Query suffix?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that the norm across the project? If not, I would leave it as it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the norm for all of the golang queries, I don't know whats the norm for the cosmwasm side

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically label them the same name as the field.

Again, as long as the json stays the same, you can gladly rename the types as you wish in another PR.
These are for use inside the osmosis Golang code, so it should be whatever idiom looks good to you.

Copy link
Member

@ValarDragon ValarDragon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM!

@ethanfrey
Copy link
Contributor Author

Merged main and fixed conflicts.

I will merge PR once CI passes.

@ethanfrey ethanfrey merged commit 12630b8 into main Mar 31, 2022
@ethanfrey ethanfrey deleted the wasm-integration-2 branch March 31, 2022 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants