diff --git a/CHANGELOG.md b/CHANGELOG.md index a46ea81b20..94ca20625b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +- [2121](https://github.com/umee-network/umee/pull/2121) Allow `MsgLeveragedLiquidate` to auto-select repay and reward denoms if request fields left blank. +- [2114](https://github.com/umee-network/umee/pull/2114) Add borrow factor to `x/leverage` - [2102](https://github.com/umee-network/umee/pull/2102) and [2106](https://github.com/umee-network/umee/pull/2106) Add `MsgLeveragedLiquidate` to `x/leverage` - [2085](https://github.com/umee-network/umee/pull/2085) Add `inspect` query to leverage module, which msut be enabled on a node by running with `-l` liquidator query flag. - [1952](https://github.com/umee-network/umee/pull/1952) Add `x/incentive` module. diff --git a/app/upgrades.go b/app/upgrades.go index 28f7e86e3a..d507b20ec2 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -52,10 +52,29 @@ func (app UmeeApp) RegisterUpgradeHandlers(bool) { app.registerUpgrade4_3(upgradeInfo) app.registerUpgrade("v4.4", upgradeInfo) app.registerUpgrade("v5.0", upgradeInfo, ugov.ModuleName, wasm.ModuleName) - app.registerUpgrade("v5.1-alpha1", upgradeInfo, incentive.ModuleName) + app.registerUpgrade5_1(upgradeInfo) // TODO: set correct 5.1 name and add borrowFactor migration } +func (app *UmeeApp) registerUpgrade5_1(upgradeInfo upgradetypes.Plan) { + planName := "v5.1" + app.UpgradeKeeper.SetUpgradeHandler(planName, + func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + app.storeUpgrade(planName, upgradeInfo, storetypes.StoreUpgrades{ + Added: []string{incentive.ModuleName}, + }) + + // TODO: set the correct drain account. This will panic if executed. + if err := app.GravityKeeper.MigrateFundsToDrainAccount( + ctx, + sdk.MustAccAddressFromBech32("the_drain_account"), + ); err != nil { + return nil, err + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +} + // performs upgrade from v4.2 to v4.3 func (app *UmeeApp) registerUpgrade4_3(upgradeInfo upgradetypes.Plan) { const planName = "v4.3" diff --git a/go.mod b/go.mod index 3a41fa5d4a..0afbff0690 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/cosmos/cosmos-sdk v0.46.13 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ibc-go/v6 v6.2.0 - github.com/ethereum/go-ethereum v1.12.0 github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 @@ -119,6 +118,7 @@ require ( github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/esimonov/ifshort v1.0.4 // indirect + github.com/ethereum/go-ethereum v1.12.0 // indirect github.com/ettle/strcase v0.1.1 // indirect github.com/fatih/color v1.15.0 // indirect github.com/fatih/structtag v1.2.0 // indirect @@ -131,7 +131,6 @@ require ( github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect @@ -264,7 +263,6 @@ require ( github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect github.com/securego/gosec/v2 v2.16.0 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect - github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/nosnakecase v1.7.0 // indirect @@ -287,8 +285,6 @@ require ( github.com/tidwall/btree v1.5.0 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect - github.com/tklauser/go-sysconf v0.3.11 // indirect - github.com/tklauser/numcpus v0.6.0 // indirect github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ulikunitz/xz v0.5.10 // indirect @@ -327,7 +323,6 @@ require ( google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect honnef.co/go/tools v0.4.3 // indirect mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect @@ -339,7 +334,7 @@ require ( replace ( github.com/CosmWasm/wasmd => github.com/notional-labs/wasmd v0.31.0-umee.46 - github.com/Gravity-Bridge/Gravity-Bridge/module => github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-9 + github.com/Gravity-Bridge/Gravity-Bridge/module => github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-10 github.com/cosmos/cosmos-sdk => github.com/umee-network/cosmos-sdk v0.46.13-umee // dgrijalva/jwt-go is deprecated and doesn't receive security updates. github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.4.2 diff --git a/go.sum b/go.sum index 6d15ad5091..8ea76af234 100644 --- a/go.sum +++ b/go.sum @@ -453,10 +453,8 @@ github.com/cosmos/ibc-go/v6 v6.2.0/go.mod h1:+S3sxcNwOhgraYDJAhIFDg5ipXHaUnJrg7t github.com/cosmos/interchain-accounts v0.4.3 h1:WedxEa/Hj/2GY7AF6CafkEPJ/Z9rhl3rT1mRwNHsdts= github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= @@ -562,7 +560,6 @@ github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8S github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -577,7 +574,6 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -613,7 +609,6 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -669,7 +664,6 @@ github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -866,7 +860,6 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -909,7 +902,6 @@ github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSV github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= @@ -917,7 +909,6 @@ github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -940,7 +931,6 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -1113,7 +1103,6 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= @@ -1312,9 +1301,7 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= @@ -1392,7 +1379,6 @@ github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1Fof github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1454,10 +1440,8 @@ github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDW github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= -github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= -github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= @@ -1468,7 +1452,6 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -1480,17 +1463,15 @@ github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iL github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-9 h1:uo8+3gFo/EhQtjbiP7AfN0Pqx6ddFf9iWcvlfeChNsY= -github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-9/go.mod h1:NR6UwQPZUoLckpOtCxgROWNEDzepe2JhxQ2u9cL+pbo= +github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-10 h1:aKj8zwDz6vZO/mOVCgLvEKI9PbZaF3AH4UJKv5npElg= +github.com/umee-network/Gravity-Bridge/module v1.5.3-umee-10/go.mod h1:NR6UwQPZUoLckpOtCxgROWNEDzepe2JhxQ2u9cL+pbo= github.com/umee-network/bech32-ibc v0.3.3 h1:wUX5uSYZl8yiFdttOvunfRihsE4miYmzl7pK2FEUs+U= github.com/umee-network/bech32-ibc v0.3.3/go.mod h1:UbhzCKN+Z7RoUdCkAanmIy+wufwQ/aQJrDEoVORhC2Y= github.com/umee-network/cosmos-sdk v0.46.13-umee h1:EeSalZHGoWdkKkCNhNd80jzRMNEQWLyDPUU5aUJQpIs= github.com/umee-network/cosmos-sdk v0.46.13-umee/go.mod h1:EfY521ATNEla8eJ6oJuZBdgP5+p360s7InnRqX+TWdM= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y= github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -1513,7 +1494,6 @@ github.com/xen0n/gosmopolitan v1.2.1/go.mod h1:JsHq/Brs1o050OOdmzHeOr0N7OtlnKRAG github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= @@ -1921,7 +1901,6 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2270,8 +2249,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/proto/umee/leverage/v1/tx.proto b/proto/umee/leverage/v1/tx.proto index a4e62a0dfd..2019ee8a57 100644 --- a/proto/umee/leverage/v1/tx.proto +++ b/proto/umee/leverage/v1/tx.proto @@ -55,7 +55,9 @@ service Msg { // this initial borrow to exceed the liquidator's borrow limit as long as it is healthy by the end // of the transaction. Repay amount is calculated automatically, so the liquidator only specifies // repay and reward token denoms. For safety, the liquidator cannot exceed 80% of their borrow limit when - // executing this transaction, instead of the regular 100%. + // executing this transaction, instead of the regular 100%. Also allows repayment and reward denoms to + // be left blank - if not specified, the module will automatically select the first (alphabetically by denom) + // borrow and/or collateral on the target account and the proceed normally. rpc LeveragedLiquidate(MsgLeveragedLiquidate) returns (MsgLeveragedLiquidateResponse); // SupplyCollateral combines the Supply and Collateralize actions. diff --git a/tests/e2e/doc.go b/tests/e2e/doc.go index 89e6c996ab..1e5fb382bc 100644 --- a/tests/e2e/doc.go +++ b/tests/e2e/doc.go @@ -1,17 +1,6 @@ // package e2e defines an integration testing suite used for full end-to-end // testing functionality. // -// The file e2e_suite_test.go defines the testing suite and contains the core -// bootrapping logic that creates a testing environment via Docker containers. // A testing network is created dynamically and contains multiple Docker -// containers: -// -// 1. A single validator Gaia network -// 2. A configurable number of Umee validator processes -// 3. A hermes relayer connecting the Umee and Gaia networks over IBC -// 4. A single Ethereum node -// 5. A configurable number of Peggy orchestrator processes -// -// The file e2e_test.go contains the actual end-to-end integration tests that -// utilize the testing suite. +// containers. package e2e diff --git a/tests/e2e/e2e_ibc_test.go b/tests/e2e/e2e_ibc_test.go index a7ee376771..ec8e959c78 100644 --- a/tests/e2e/e2e_ibc_test.go +++ b/tests/e2e/e2e_ibc_test.go @@ -56,7 +56,6 @@ func (s *E2ETest) checkSupply(endpoint, ibcDenom string, amount math.Int) { func (s *E2ETest) TestIBCTokenTransfer() { // s.T().Parallel() - var ibcStakeDenom string s.Run("ibc_transfer_quota", func() { // require the recipient account receives the IBC tokens (IBC packets ACKd) @@ -226,33 +225,4 @@ func (s *E2ETest) TestIBCTokenTransfer() { // s.checkSupply(umeeAPIEndpoint, stakeIBCHash, math.ZeroInt()) s.checkSupply(umeeAPIEndpoint, stakeIBCHash, token.Amount) }) - - var ibcStakeERC20Addr string - s.Run("deploy_stake_erc20 ibcStakeERC20Addr", func() { - s.T().Skip("paused due to Ethereum PoS migration and PoW fork") - s.Require().NotEmpty(ibcStakeDenom) - ibcStakeERC20Addr = s.DeployERC20Token(ibcStakeDenom) - }) - - // send 300 stake tokens from Umee to Ethereum - s.Run("send_stake_tokens_to_eth", func() { - s.T().Skip("paused due to Ethereum PoS migration and PoW fork") - umeeValIdxSender := 0 - orchestratorIdxReceiver := 1 - amount := sdk.NewCoin(ibcStakeDenom, math.NewInt(300)) - umeeFee := sdk.NewCoin(appparams.BondDenom, math.NewInt(10000)) - gravityFee := sdk.NewCoin(ibcStakeDenom, math.NewInt(7)) - - s.SendFromUmeeToEthCheck(umeeValIdxSender, orchestratorIdxReceiver, ibcStakeERC20Addr, amount, umeeFee, gravityFee) - }) - - // send 300 stake tokens from Ethereum back to Umee - s.Run("send_stake_tokens_from_eth", func() { - s.T().Skip("paused due to Ethereum PoS migration and PoW fork") - umeeValIdxReceiver := 0 - orchestratorIdxSender := 1 - amount := uint64(300) - - s.SendFromEthToUmeeCheck(orchestratorIdxSender, umeeValIdxReceiver, ibcStakeERC20Addr, ibcStakeDenom, amount) - }) } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 59be5fc846..1da5d3aa98 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -3,11 +3,8 @@ package e2e import ( "testing" - "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" - appparams "github.com/umee-network/umee/v5/app/params" setup "github.com/umee-network/umee/v5/tests/e2e/setup" "github.com/umee-network/umee/v5/tests/grpc" ) @@ -20,65 +17,6 @@ func TestE2ETestSuite(t *testing.T) { suite.Run(t, new(E2ETest)) } -func (s *E2ETest) TestPhotonTokenTransfers() { - s.T().Skip("paused due to Ethereum PoS migration and PoW fork") - // deploy photon ERC20 token contact - var photonERC20Addr string - s.Run("deploy_photon_erc20", func() { - photonERC20Addr = s.DeployERC20Token(setup.PhotonDenom) - }) - - // send 100 photon tokens from Umee to Ethereum - s.Run("send_photon_tokens_to_eth", func() { - umeeValIdxSender := 0 - orchestratorIdxReceiver := 1 - amount := sdk.NewCoin(setup.PhotonDenom, math.NewInt(100)) - umeeFee := sdk.NewCoin(appparams.BondDenom, math.NewInt(10000)) - gravityFee := sdk.NewCoin(setup.PhotonDenom, math.NewInt(3)) - - s.SendFromUmeeToEthCheck(umeeValIdxSender, orchestratorIdxReceiver, photonERC20Addr, amount, umeeFee, gravityFee) - }) - - // send 100 photon tokens from Ethereum back to Umee - s.Run("send_photon_tokens_from_eth", func() { - s.T().Skip("paused due to Ethereum PoS migration and PoW fork") - umeeValIdxReceiver := 0 - orchestratorIdxSender := 1 - amount := uint64(100) - - s.SendFromEthToUmeeCheck(orchestratorIdxSender, umeeValIdxReceiver, photonERC20Addr, setup.PhotonDenom, amount) - }) -} - -func (s *E2ETest) TestUmeeTokenTransfers() { - s.T().Skip("paused due to Ethereum PoS migration and PoW fork") - // deploy umee ERC20 token contract - var umeeERC20Addr string - s.Run("deploy_umee_erc20", func() { - umeeERC20Addr = s.DeployERC20Token(appparams.BondDenom) - }) - - // send 300 umee tokens from Umee to Ethereum - s.Run("send_uumee_tokens_to_eth", func() { - umeeValIdxSender := 0 - orchestratorIdxReceiver := 1 - amount := sdk.NewCoin(appparams.BondDenom, math.NewInt(300)) - umeeFee := sdk.NewCoin(appparams.BondDenom, math.NewInt(10000)) - gravityFee := sdk.NewCoin(appparams.BondDenom, math.NewInt(7)) - - s.SendFromUmeeToEthCheck(umeeValIdxSender, orchestratorIdxReceiver, umeeERC20Addr, amount, umeeFee, gravityFee) - }) - - // send 300 umee tokens from Ethereum back to Umee - s.Run("send_uumee_tokens_from_eth", func() { - umeeValIdxReceiver := 0 - orchestratorIdxSender := 1 - amount := uint64(300) - - s.SendFromEthToUmeeCheck(orchestratorIdxSender, umeeValIdxReceiver, umeeERC20Addr, appparams.BondDenom, amount) - }) -} - // TestMedians queries for the oracle params, collects historical // prices based on those params, checks that the stored medians and // medians deviations are correct, updates the oracle params with diff --git a/tests/e2e/setup/chain.go b/tests/e2e/setup/chain.go index 717a2a0b1d..bb77f13894 100644 --- a/tests/e2e/setup/chain.go +++ b/tests/e2e/setup/chain.go @@ -43,7 +43,6 @@ type chain struct { dataDir string ID string Validators []*validator - Orchestrators []*orchestrator GaiaValidators []*gaiaValidator } @@ -107,27 +106,6 @@ func (c *chain) createAndInitGaiaValidator(cdc codec.Codec) error { return nil } -func (c *chain) createAndInitOrchestrators(cdc codec.Codec, count int) error { - for i := 0; i < count; i++ { - // create orchestrator - orchestrator := c.createOrchestrator(i) - - err := orchestrator.createKey(cdc, "orch") - if err != nil { - return err - } - - err = orchestrator.generateEthereumKey() - if err != nil { - return err - } - - c.Orchestrators = append(c.Orchestrators, orchestrator) - } - - return nil -} - func (c *chain) createValidator(index int) *validator { return &validator{ chain: c, @@ -136,12 +114,6 @@ func (c *chain) createValidator(index int) *validator { } } -func (c *chain) createOrchestrator(index int) *orchestrator { - return &orchestrator{ - Index: index, - } -} - func (c *chain) createGaiaValidator(index int) *gaiaValidator { return &gaiaValidator{ index: index, diff --git a/tests/e2e/setup/eth_abi.go b/tests/e2e/setup/eth_abi.go deleted file mode 100644 index 590536d2c1..0000000000 --- a/tests/e2e/setup/eth_abi.go +++ /dev/null @@ -1,33 +0,0 @@ -package setup - -import ( - "github.com/ethereum/go-ethereum/accounts/abi" -) - -const ( - AbiMethodNameBalanceOf = "balanceOf" -) - -var ( - AbiAddressTy, _ = abi.NewType("address", "", nil) - AbiUint256Ty, _ = abi.NewType("uint256", "", nil) - - EthABI = abi.ABI{ - Methods: map[string]abi.Method{ - AbiMethodNameBalanceOf: abi.NewMethod( - AbiMethodNameBalanceOf, - AbiMethodNameBalanceOf, - abi.Function, - "view", - false, - false, - abi.Arguments{ - {Name: "account", Type: AbiAddressTy}, - }, - abi.Arguments{ - {Type: AbiUint256Ty}, - }, - ), - }, - } -) diff --git a/tests/e2e/setup/ethereum.go b/tests/e2e/setup/ethereum.go deleted file mode 100644 index f514ddc496..0000000000 --- a/tests/e2e/setup/ethereum.go +++ /dev/null @@ -1,25 +0,0 @@ -package setup - -type EthereumConfig struct { - ChainID uint `json:"chainId"` - HomesteadBlock uint `json:"homesteadBlock"` - EIP150Block uint `json:"eip150Block"` - EIP155Block uint `json:"eip155Block"` - EIP158Block uint `json:"eip158Block"` - ByzantiumBlock uint `json:"byzantiumBlock"` - ConstantinopleBlock uint `json:"constantinopleBlock"` - PetersburgBlock uint `json:"petersburgBlock"` - IstanbulBlock uint `json:"istanbulBlock"` - BerlinBlock uint `json:"berlinBlock"` -} - -type Allocation struct { - Balance string `json:"balance"` -} - -type EthereumGenesis struct { - Difficulty string `json:"difficulty"` - GasLimit string `json:"gasLimit"` - Config EthereumConfig `json:"config"` - Alloc map[string]Allocation `json:"alloc"` -} diff --git a/tests/e2e/setup/orchestrator.go b/tests/e2e/setup/orchestrator.go deleted file mode 100644 index e70f19d008..0000000000 --- a/tests/e2e/setup/orchestrator.go +++ /dev/null @@ -1,101 +0,0 @@ -package setup - -import ( - "crypto/ecdsa" - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - sdkcrypto "github.com/cosmos/cosmos-sdk/crypto" - "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" -) - -type orchestrator struct { - Index int - Mnemonic string - keyInfo keyring.Record - PrivateKey cryptotypes.PrivKey - EthereumKey ethereumKey -} - -type ethereumKey struct { - PublicKey string - PrivateKey string - Address string -} - -func (o *orchestrator) instanceName() string { - return fmt.Sprintf("orchestrator%d", o.Index) -} - -func (o *orchestrator) generateEthereumKey() error { - privateKey, err := crypto.GenerateKey() - if err != nil { - return err - } - - privateKeyBytes := crypto.FromECDSA(privateKey) - - publicKey := privateKey.Public() - publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) - if !ok { - return fmt.Errorf("unexpected public key type; expected: %T, got: %T", &ecdsa.PublicKey{}, publicKey) - } - - publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) - - o.EthereumKey = ethereumKey{ - PrivateKey: hexutil.Encode(privateKeyBytes), - PublicKey: hexutil.Encode(publicKeyBytes), - Address: crypto.PubkeyToAddress(*publicKeyECDSA).Hex(), - } - - return nil -} - -func (o *orchestrator) createKey(cdc codec.Codec, name string) error { - mnemonic, err := createMnemonic() - if err != nil { - return err - } - - return o.createKeyFromMnemonic(cdc, name, mnemonic) -} - -func (o *orchestrator) createKeyFromMnemonic(cdc codec.Codec, name, mnemonic string) error { - kb, err := keyring.New(keyringAppName, keyring.BackendMemory, "", nil, cdc) - if err != nil { - return err - } - - keyringAlgos, _ := kb.SupportedAlgorithms() - algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyringAlgos) - if err != nil { - return err - } - - info, err := kb.NewAccount(name, mnemonic, "", sdk.FullFundraiserPath, algo) - if err != nil { - return err - } - - privKeyArmor, err := kb.ExportPrivKeyArmor(name, keyringPassphrase) - if err != nil { - return err - } - - privKey, _, err := sdkcrypto.UnarmorDecryptPrivKey(privKeyArmor, keyringPassphrase) - if err != nil { - return err - } - - o.keyInfo = *info - o.Mnemonic = mnemonic - o.PrivateKey = privKey - - return nil -} diff --git a/tests/e2e/setup/setup.go b/tests/e2e/setup/setup.go index 6e89b63251..0a3ec88a8f 100644 --- a/tests/e2e/setup/setup.go +++ b/tests/e2e/setup/setup.go @@ -10,7 +10,6 @@ import ( "os" "path" "path/filepath" - "regexp" "strconv" "strings" "time" @@ -22,8 +21,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" bech32ibctypes "github.com/osmosis-labs/bech32-ibc/x/bech32ibc/types" @@ -49,11 +46,9 @@ type E2ETestSuite struct { tmpDirs []string Chain *chain - EthClient *ethclient.Client gaiaRPC *rpchttp.HTTP DkrPool *dockertest.Pool DkrNet *dockertest.Network - EthResource *dockertest.Resource GaiaResource *dockertest.Resource HermesResource *dockertest.Resource priceFeederResource *dockertest.Resource @@ -84,31 +79,7 @@ func (s *E2ETestSuite) SetupSuite() { s.DkrNet, err = s.DkrPool.CreateNetwork(fmt.Sprintf("%s-testnet", s.Chain.ID)) s.Require().NoError(err) - s.T().Logf("Ethereum and peggo are disable due to Ethereum PoS migration and PoW fork") - // var useGanache bool - // if str := os.Getenv("UMEE_E2E_USE_GANACHE"); len(str) > 0 { - // useGanache, err = strconv.ParseBool(str) - // s.Require().NoError(err) - // } - - // The bootstrapping phase is as follows: - // - // 1. Initialize Umee validator nodes. - // 2. Launch an Ethereum container that mines. - // 3. Create and initialize Umee validator genesis files (setting delegate keys for validators). - // 4. Start Umee network. - // 5. Run an Oracle price feeder. - // 6. Create and run Gaia container(s). - // 7. Create and run IBC relayer (Hermes) containers. - // 8. Deploy the Gravity Bridge contract. - // 9. Create and start Peggo (orchestrator) containers. - s.initNodes() - // if useGanache { - // s.runGanacheContainer() - // } else { - // s.initEthereum() - // s.runEthContainer() - // } + s.initNodes() // init validator nodes s.initGenesis() s.initValidatorConfigs() s.runValidators() @@ -119,8 +90,6 @@ func (s *E2ETestSuite) SetupSuite() { } else { s.T().Log("running minimum network withut gaia,price-feeder and ibc-relayer") } - // s.runContractDeployment() - // s.runOrchestrators() s.initUmeeClient() } @@ -136,7 +105,6 @@ func (s *E2ETestSuite) TearDownSuite() { s.T().Log("tearing down e2e integration test suite...") - // s.Require().NoError(s.DkrPool.Purge(s.ethResource)) if !s.MinNetwork { s.Require().NoError(s.DkrPool.Purge(s.GaiaResource)) s.Require().NoError(s.DkrPool.Purge(s.HermesResource)) @@ -147,10 +115,6 @@ func (s *E2ETestSuite) TearDownSuite() { s.Require().NoError(s.DkrPool.Purge(vc)) } - // for _, oc := range s.OrchResources { - // s.Require().NoError(s.DkrPool.Purge(oc)) - // } - s.Require().NoError(s.DkrPool.RemoveNetwork(s.DkrNet)) os.RemoveAll(s.Chain.dataDir) @@ -161,7 +125,6 @@ func (s *E2ETestSuite) TearDownSuite() { func (s *E2ETestSuite) initNodes() { s.Require().NoError(s.Chain.createAndInitValidators(s.cdc, 3)) - s.Require().NoError(s.Chain.createAndInitOrchestrators(s.cdc, 3)) s.Require().NoError(s.Chain.createAndInitGaiaValidator(s.cdc)) // initialize a genesis file for the first validator @@ -174,16 +137,6 @@ func (s *E2ETestSuite) initNodes() { ) } - // add orchestrator accounts to genesis file - for _, orch := range s.Chain.Orchestrators { - orchAddr, err := orch.keyInfo.GetAddress() - s.Require().NoError(err) - - s.Require().NoError( - addGenesisAccount(s.cdc, val0ConfigDir, "", InitBalanceStr, orchAddr), - ) - } - // copy the genesis file to the remaining validators for _, val := range s.Chain.Validators[1:] { _, err := copyFile( @@ -194,31 +147,6 @@ func (s *E2ETestSuite) initNodes() { } } -func (s *E2ETestSuite) initEthereum() { - // generate ethereum keys for validators add them to the ethereum genesis - ethGenesis := EthereumGenesis{ - Difficulty: "0x400", - GasLimit: "0xB71B00", - Config: EthereumConfig{ChainID: EthChainID}, - Alloc: make(map[string]Allocation, len(s.Chain.Validators)+1), - } - - alloc := Allocation{ - Balance: "0x1337000000000000000000", - } - - ethGenesis.Alloc["0xBf660843528035a5A4921534E156a27e64B231fE"] = alloc - for _, orch := range s.Chain.Orchestrators { - s.Require().NoError(orch.generateEthereumKey()) - ethGenesis.Alloc[orch.EthereumKey.Address] = alloc - } - ethGenBz, err := json.MarshalIndent(ethGenesis, "", " ") - s.Require().NoError(err) - - // write out the genesis file - s.Require().NoError(writeFile(filepath.Join(s.Chain.configDir(), "eth_genesis.json"), ethGenBz)) -} - func (s *E2ETestSuite) initGenesis() { serverCtx := server.NewDefaultContext() config := serverCtx.Config @@ -346,13 +274,7 @@ func (s *E2ETestSuite) initGenesis() { } s.Require().NoError(err) - orchAddr, err := s.Chain.Orchestrators[i].keyInfo.GetAddress() - s.Require().NoError(err) - - delKeysMsg, err := val.buildDelegateKeysMsg(orchAddr, s.Chain.Orchestrators[i].EthereumKey.Address) - s.Require().NoError(err) - - signedTx, err := val.signMsg(s.cdc, createValmsg, delKeysMsg) + signedTx, err := val.signMsg(s.cdc, createValmsg) s.Require().NoError(err) txRaw, err := s.cdc.MarshalJSON(signedTx) @@ -426,147 +348,6 @@ func (s *E2ETestSuite) initValidatorConfigs() { } } -func (s *E2ETestSuite) runGanacheContainer() { - s.T().Log("starting Ganache container...") - - tmpDir, err := os.MkdirTemp("", "umee-e2e-testnet-eth-") - s.Require().NoError(err) - s.tmpDirs = append(s.tmpDirs, tmpDir) - - _, err = copyFile( - filepath.Join("./docker/", "ganache.Dockerfile"), - filepath.Join(tmpDir, "ganache.Dockerfile"), - ) - s.Require().NoError(err) - - entrypoint := []string{ - "ganache-cli", - "-h", - "0.0.0.0", - "--networkId", - "15", - } - - entrypoint = append(entrypoint, "--account", "0xb1bab011e03a9862664706fc3bbaa1b16651528e5f0e7fbfcbfdd8be302a13e7,0x3635C9ADC5DEA00000") - for _, orch := range s.Chain.Orchestrators { - s.Require().NoError(orch.generateEthereumKey()) - entrypoint = append(entrypoint, "--account", orch.EthereumKey.PrivateKey+",0x3635C9ADC5DEA00000") - } - - s.EthResource, err = s.DkrPool.BuildAndRunWithBuildOptions( - &dockertest.BuildOptions{ - Dockerfile: "ganache.Dockerfile", - ContextDir: tmpDir, - }, - &dockertest.RunOptions{ - Name: "ganache", - NetworkID: s.DkrNet.Network.ID, - ExposedPorts: []string{"8545"}, - PortBindings: map[docker.Port][]docker.PortBinding{ - "8545/tcp": {{HostIP: "", HostPort: "8545"}}, - }, - Env: []string{}, - Entrypoint: entrypoint, - }, - noRestart, - ) - s.Require().NoError(err) - - s.EthClient, err = ethclient.Dial(fmt.Sprintf("http://%s", s.EthResource.GetHostPort("8545/tcp"))) - s.Require().NoError(err) - - match := "Listening on 0.0.0.0:8545" - - var ( - outBuf bytes.Buffer - errBuf bytes.Buffer - ) - - // Wait for Ganache to start running. - s.Require().Eventually( - func() bool { - err := s.DkrPool.Client.Logs( - docker.LogsOptions{ - Container: s.EthResource.Container.ID, - OutputStream: &outBuf, - ErrorStream: &errBuf, - Stdout: true, - Stderr: true, - }, - ) - if err != nil { - return false - } - - return strings.Contains(outBuf.String(), match) - }, - 1*time.Minute, - 5*time.Second, - "ganache node failed to start", - ) - s.T().Logf("started Ganache container: %s", s.EthResource.Container.ID) -} - -func (s *E2ETestSuite) runEthContainer() { - s.T().Log("starting Ethereum container...") - tmpDir, err := os.MkdirTemp("", "umee-e2e-testnet-eth-") - s.Require().NoError(err) - s.tmpDirs = append(s.tmpDirs, tmpDir) - - _, err = copyFile( - filepath.Join(s.Chain.configDir(), "eth_genesis.json"), - filepath.Join(tmpDir, "eth_genesis.json"), - ) - s.Require().NoError(err) - - _, err = copyFile( - filepath.Join("./docker/", "eth.Dockerfile"), - filepath.Join(tmpDir, "eth.Dockerfile"), - ) - s.Require().NoError(err) - - s.EthResource, err = s.DkrPool.BuildAndRunWithBuildOptions( - &dockertest.BuildOptions{ - Dockerfile: "eth.Dockerfile", - ContextDir: tmpDir, - }, - &dockertest.RunOptions{ - Name: "ethereum", - NetworkID: s.DkrNet.Network.ID, - PortBindings: map[docker.Port][]docker.PortBinding{ - "8545/tcp": {{HostIP: "", HostPort: "8545"}}, - }, - Env: []string{}, - }, - noRestart, - ) - s.Require().NoError(err) - - s.EthClient, err = ethclient.Dial(fmt.Sprintf("http://%s", s.EthResource.GetHostPort("8545/tcp"))) - s.Require().NoError(err) - - // Wait for the Ethereum node to start producing blocks; DAG completion takes - // about two minutes. - s.Require().Eventually( - func() bool { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - - height, err := s.EthClient.BlockNumber(ctx) - if err != nil { - return false - } - - return height > 1 - }, - 5*time.Minute, - 10*time.Second, - "geth node failed to produce a block", - ) - - s.T().Logf("started Ethereum container: %s", s.EthResource.Container.ID) -} - func (s *E2ETestSuite) runValidators() { s.T().Log("starting Umee validator containers...") @@ -793,177 +574,6 @@ func (s *E2ETestSuite) runIBCRelayer() { s.connectIBCChains() } -func (s *E2ETestSuite) runContractDeployment() { - s.T().Log("starting Gravity Bridge contract deployer container...") - - resource, err := s.DkrPool.RunWithOptions( - &dockertest.RunOptions{ - Name: "gravity-contract-deployer", - NetworkID: s.DkrNet.Network.ID, - Repository: "umee-network/umeed-e2e", - // NOTE: container names are prefixed with '/' - Env: []string{"PEGGO_ETH_PK=" + EthMinerPK}, - Entrypoint: []string{ - "peggo", - "bridge", - "deploy-gravity", - "--eth-rpc", - fmt.Sprintf("http://%s:8545", s.EthResource.Container.Name[1:]), - "--cosmos-grpc", - fmt.Sprintf("tcp://%s:9090", s.ValResources[0].Container.Name[1:]), - "--tendermint-rpc", - fmt.Sprintf("http://%s:26657", s.ValResources[0].Container.Name[1:]), - }, - }, - noRestart, - ) - s.Require().NoError(err) - - s.T().Logf("started contract deployer: %s", resource.Container.ID) - - // wait for the container to finish executing - container := resource.Container - for container.State.Running { - time.Sleep(10 * time.Second) - - container, err = s.DkrPool.Client.InspectContainer(resource.Container.ID) - s.Require().NoError(err) - } - - var ( - outBuf bytes.Buffer - errBuf bytes.Buffer - ) - - s.Require().NoErrorf(s.DkrPool.Client.Logs( - docker.LogsOptions{ - Container: resource.Container.ID, - OutputStream: &outBuf, - ErrorStream: &errBuf, - Stdout: true, - Stderr: true, - }, - ), - "failed to start contract deployer; stdout: %s, stderr: %s", - outBuf.String(), errBuf.String(), - ) - - re := regexp.MustCompile(`Address: (0x.+)`) - tokens := re.FindStringSubmatch(errBuf.String()) - s.Require().Len(tokens, 2) - - gravityContractAddr := tokens[1] - s.Require().NotEmpty(gravityContractAddr) - - re = regexp.MustCompile(`Transaction: (0x.+)`) - tokens = re.FindStringSubmatch(errBuf.String()) - s.Require().Len(tokens, 2) - - txHash := tokens[1] - s.Require().NotEmpty(txHash) - - s.Require().Eventually( - func() bool { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := s.QueryEthTx(ctx, s.EthClient, txHash); err != nil { - return false - } - - return true - }, - time.Minute, - time.Second, - "failed to confirm Peggy contract deployment transaction", - ) - - s.Require().NoError(s.DkrPool.RemoveContainerByName(container.Name)) - - s.T().Logf("deployed Gravity Bridge contract: %s", gravityContractAddr) - s.GravityContractAddr = gravityContractAddr -} - -func (s *E2ETestSuite) runOrchestrators() { - s.T().Log("starting orchestrator containers...") - - s.OrchResources = make([]*dockertest.Resource, len(s.Chain.Validators)) - for i, orch := range s.Chain.Orchestrators { - resource, err := s.DkrPool.RunWithOptions( - &dockertest.RunOptions{ - Name: s.Chain.Orchestrators[i].instanceName(), - NetworkID: s.DkrNet.Network.ID, - Repository: "umee-network/umeed-e2e", - Env: []string{"PEGGO_ETH_PK=" + orch.EthereumKey.PrivateKey}, - // NOTE: container names are prefixed with '/' - Entrypoint: []string{ - "peggo", - "orchestrator", - s.GravityContractAddr, - "--eth-rpc", - fmt.Sprintf("http://%s:8545", s.EthResource.Container.Name[1:]), - "--cosmos-chain-id", - s.Chain.ID, - "--cosmos-grpc", - fmt.Sprintf("tcp://%s:9090", s.ValResources[i].Container.Name[1:]), - "--tendermint-rpc", - fmt.Sprintf("http://%s:26657", s.ValResources[i].Container.Name[1:]), - "--cosmos-gas-prices", - minGasPrice, - "--cosmos-from", - orch.keyInfo.Name, - "--relay-batches=true", - "--valset-relay-mode=minimum", - "--profit-multiplier=0.0", - "--oracle-providers=mock", - "--relayer-loop-multiplier=1.0", - "--requester-loop-multiplier=1.0", - "--cosmos-pk", - hexutil.Encode(s.Chain.Orchestrators[i].PrivateKey.Bytes()), - }, - }, - noRestart, - ) - s.Require().NoError(err) - - s.OrchResources[i] = resource - s.T().Logf("started orchestrator container: %s", resource.Container.ID) - } - - match := "oracle sent set of claims successfully" - for _, resource := range s.OrchResources { - s.T().Logf("waiting for orchestrator to be healthy: %s", resource.Container.ID) - - var ( - outBuf bytes.Buffer - errBuf bytes.Buffer - ) - - s.Require().Eventuallyf( - func() bool { - err := s.DkrPool.Client.Logs( - docker.LogsOptions{ - Container: resource.Container.ID, - OutputStream: &outBuf, - ErrorStream: &errBuf, - Stdout: true, - Stderr: true, - }, - ) - if err != nil { - return false - } - - return strings.Contains(errBuf.String(), match) - }, - 5*time.Minute, - time.Second, - "orchestrator %s not healthy", - resource.Container.ID, - ) - } -} - func (s *E2ETestSuite) runPriceFeeder() { s.T().Log("starting price-feeder container...") diff --git a/tests/e2e/setup/utils.go b/tests/e2e/setup/utils.go index d0fc3cbfe9..dbbe15b974 100644 --- a/tests/e2e/setup/utils.go +++ b/tests/e2e/setup/utils.go @@ -7,21 +7,14 @@ import ( "fmt" "io" "net/http" - "regexp" - "strconv" "strings" "time" - gravitytypes "github.com/Gravity-Bridge/Gravity-Bridge/module/x/gravity/types" - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/unknownproto" sdk "github.com/cosmos/cosmos-sdk/types" sdktx "github.com/cosmos/cosmos-sdk/types/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/ethereum/go-ethereum" - ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" "github.com/gogo/protobuf/proto" "github.com/ory/dockertest/v3/docker" @@ -37,328 +30,6 @@ func (s *E2ETestSuite) GaiaREST() string { return fmt.Sprintf("http://%s", s.GaiaResource.GetHostPort("1317/tcp")) } -func (s *E2ETestSuite) DeployERC20Token(baseDenom string) string { - s.T().Logf("deploying ERC20 token contract: %s", baseDenom) - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - exec, err := s.DkrPool.Client.CreateExec(docker.CreateExecOptions{ - Context: ctx, - AttachStdout: true, - AttachStderr: true, - Container: s.OrchResources[0].Container.ID, - User: "root", - Env: []string{"PEGGO_ETH_PK=" + EthMinerPK}, - Cmd: []string{ - "peggo", - "bridge", - "deploy-erc20", - s.GravityContractAddr, - baseDenom, - "--eth-rpc", - fmt.Sprintf("http://%s:8545", s.EthResource.Container.Name[1:]), - "--cosmos-chain-id", - s.Chain.ID, - "--cosmos-grpc", - fmt.Sprintf("tcp://%s:9090", s.ValResources[0].Container.Name[1:]), - "--tendermint-rpc", - fmt.Sprintf("http://%s:26657", s.ValResources[0].Container.Name[1:]), - }, - }) - s.Require().NoError(err) - - var ( - outBuf bytes.Buffer - errBuf bytes.Buffer - ) - - err = s.DkrPool.Client.StartExec(exec.ID, docker.StartExecOptions{ - Context: ctx, - Detach: false, - OutputStream: &outBuf, - ErrorStream: &errBuf, - }) - s.Require().NoErrorf( - err, - "failed to get ERC20 deployment logs; stdout: %s, stderr: %s", outBuf.String(), errBuf.String(), - ) - - re := regexp.MustCompile(`Transaction: (0x.+)`) - tokens := re.FindStringSubmatch(errBuf.String()) - s.Require().Lenf(tokens, 2, "stderr: %s", errBuf.String()) - - txHash := tokens[1] - s.Require().NotEmpty(txHash) - - s.Require().Eventually( - func() bool { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := s.QueryEthTx(ctx, s.EthClient, txHash); err != nil { - return false - } - - return true - }, - 6*time.Minute, - time.Second, - "failed to confirm ERC20 deployment transaction", - ) - - umeeAPIEndpoint := fmt.Sprintf("http://%s", s.ValResources[0].GetHostPort("1317/tcp")) - - var erc20Addr string - s.Require().Eventually( - func() bool { - addr, cosmosNative, err := s.QueryDenomToERC20(umeeAPIEndpoint, baseDenom) - if err != nil { - return false - } - - if cosmosNative && len(addr) > 0 { - erc20Addr = addr - return true - } - - return false - }, - 2*time.Minute, - time.Second, - "failed to query ERC20 contract address", - ) - - s.T().Logf("deployed %s contract: %s", baseDenom, erc20Addr) - - return erc20Addr -} - -func (s *E2ETestSuite) SendFromUmeeToEth(valIdx int, ethDest, amount, umeeFee, gravityFee string) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - valAddr, err := s.Chain.Validators[valIdx].KeyInfo.GetAddress() - s.Require().NoError(err) - - s.T().Logf( - "sending tokens from Umee to Ethereum; from: %s, to: %s, amount: %s, umeeFee: %s, gravityFee: %s", - valAddr, ethDest, amount, umeeFee, gravityFee, - ) - - exec, err := s.DkrPool.Client.CreateExec(docker.CreateExecOptions{ - Context: ctx, - AttachStdout: true, - AttachStderr: true, - Container: s.ValResources[valIdx].Container.ID, - User: "root", - Cmd: []string{ - "umeed", - "tx", - "gravity", - "send-to-eth", - ethDest, - amount, - gravityFee, - fmt.Sprintf("--%s=%s", flags.FlagFrom, s.Chain.Validators[valIdx].KeyInfo.Name), - fmt.Sprintf("--%s=%s", flags.FlagChainID, s.Chain.ID), - fmt.Sprintf("--%s=%s", flags.FlagFees, umeeFee), - "--keyring-backend=test", - "--broadcast-mode=sync", - "--output=json", - "-y", - }, - }) - s.Require().NoError(err) - - var ( - outBuf bytes.Buffer - errBuf bytes.Buffer - ) - - err = s.DkrPool.Client.StartExec(exec.ID, docker.StartExecOptions{ - Context: ctx, - Detach: false, - OutputStream: &outBuf, - ErrorStream: &errBuf, - }) - s.Require().NoErrorf(err, "stdout: %s, stderr: %s", outBuf.String(), errBuf.String()) - - var broadcastResp map[string]interface{} - s.Require().NoError(json.Unmarshal(outBuf.Bytes(), &broadcastResp), outBuf.String()) - - endpoint := fmt.Sprintf("http://%s", s.ValResources[valIdx].GetHostPort("1317/tcp")) - txHash := broadcastResp["txhash"].(string) - - s.Require().Eventuallyf( - func() bool { - return s.QueryUmeeTx(endpoint, txHash) == nil - }, - 2*time.Minute, - 5*time.Second, - "stdout: %s, stderr: %s", - outBuf.String(), errBuf.String(), - ) -} - -func (s *E2ETestSuite) SendFromUmeeToEthCheck( - umeeValIdxSender, - orchestratorIdxReceiver int, - ethTokenAddr string, - amount, umeeFee, gravityFee sdk.Coin, -) { - if !strings.EqualFold(amount.Denom, gravityFee.Denom) { - s.T().Error("Amount and gravityFee should be the same denom", amount, gravityFee) - } - - // if all the coins are on the same denom - allSameDenom := strings.EqualFold(amount.Denom, umeeFee.Denom) && strings.EqualFold(amount.Denom, gravityFee.Denom) - var umeeFeeBalanceBeforeSend sdk.Coin - if !allSameDenom { - umeeFeeBalanceBeforeSend, _ = s.QueryUmeeBalance(umeeValIdxSender, umeeFee.Denom) - } - - umeeAmountBalanceBeforeSend, ethBalanceBeforeSend, _, ethAddr := s.QueryUmeeEthBalance(umeeValIdxSender, orchestratorIdxReceiver, amount.Denom, ethTokenAddr) // 3300000000 - - s.SendFromUmeeToEth(umeeValIdxSender, ethAddr, amount.String(), umeeFee.String(), gravityFee.String()) - umeeAmountBalanceAfterSend, ethBalanceAfterSend, _, _ := s.QueryUmeeEthBalance(umeeValIdxSender, orchestratorIdxReceiver, amount.Denom, ethTokenAddr) // 3299999693 - - if allSameDenom { - s.Require().Equal(umeeAmountBalanceBeforeSend.Sub(amount).Sub(umeeFee).Sub(gravityFee).Amount.Int64(), umeeAmountBalanceAfterSend.Amount.Int64()) - } else { // the umeeFee and amount have different denom - s.Require().Equal(umeeAmountBalanceBeforeSend.Sub(amount).Sub(gravityFee).Amount.Int64(), umeeAmountBalanceAfterSend.Amount.Int64()) - umeeFeeBalanceAfterSend, _ := s.QueryUmeeBalance(umeeValIdxSender, umeeFee.Denom) - s.Require().Equal(umeeFeeBalanceBeforeSend.Sub(umeeFee).Amount.Int64(), umeeFeeBalanceAfterSend.Amount.Int64()) - } - - // require the Ethereum recipient balance increased - // peggo needs time to read the event and cross the tx - ethLatestBalance := ethBalanceAfterSend - expectedAmount := (ethBalanceBeforeSend + int64(amount.Amount.Int64())) - s.Require().Eventuallyf( - func() bool { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - - b, err := s.QueryEthTokenBalance(ctx, s.EthClient, ethTokenAddr, ethAddr) - if err != nil { - return false - } - - ethLatestBalance = b - - // The balance could differ if the receiving address was the orchestrator - // that sent the batch tx and got the gravity fee. - return b >= expectedAmount && b <= expectedAmount+gravityFee.Amount.Int64() - }, - 2*time.Minute, - 5*time.Second, - "unexpected balance: %d", ethLatestBalance, - ) -} - -func (s *E2ETestSuite) SendFromEthToUmeeCheck( - orchestratorIdxSender, - umeeValIdxReceiver int, - ethTokenAddr, - umeeTokenDenom string, - amount uint64, -) { - umeeBalanceBeforeSend, ethBalanceBeforeSend, umeeAddr, _ := s.QueryUmeeEthBalance(umeeValIdxReceiver, orchestratorIdxSender, umeeTokenDenom, ethTokenAddr) - s.SendFromEthToUmee(orchestratorIdxSender, ethTokenAddr, umeeAddr, fmt.Sprintf("%d", amount)) - umeeBalanceAfterSend, ethBalanceAfterSend, _, _ := s.QueryUmeeEthBalance(umeeValIdxReceiver, orchestratorIdxSender, umeeTokenDenom, ethTokenAddr) - - s.Require().Equal(ethBalanceBeforeSend-int64(amount), ethBalanceAfterSend) - - umeeEndpoint := fmt.Sprintf("http://%s", s.ValResources[umeeValIdxReceiver].GetHostPort("1317/tcp")) - // require the original sender's (validator) balance increased - // peggo needs time to read the event and cross the tx - umeeLatestBalance := umeeBalanceAfterSend.Amount - s.Require().Eventuallyf( - func() bool { - b, err := s.QueryUmeeDenomBalance(umeeEndpoint, umeeAddr, umeeTokenDenom) - if err != nil { - s.T().Logf("Error at sendFromEthToUmeeCheck.queryUmeeDenomBalance %+v", err) - return false - } - - umeeLatestBalance = b.Amount - - return umeeBalanceBeforeSend.Amount.AddRaw(int64(amount)).Equal(umeeLatestBalance) - }, - 2*time.Minute, - 5*time.Second, - "unexpected balance: %d", umeeLatestBalance.Int64(), - ) -} - -func (s *E2ETestSuite) SendFromEthToUmee(valIdx int, tokenAddr, toUmeeAddr, amount string) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - s.T().Logf( - "sending tokens from Ethereum to Umee; from: %s, to: %s, amount: %s, contract: %s", - s.Chain.Orchestrators[valIdx].EthereumKey.Address, toUmeeAddr, amount, tokenAddr, - ) - - exec, err := s.DkrPool.Client.CreateExec(docker.CreateExecOptions{ - Context: ctx, - AttachStdout: true, - AttachStderr: true, - Container: s.OrchResources[valIdx].Container.ID, - User: "root", - Env: []string{"PEGGO_ETH_PK=" + s.Chain.Orchestrators[valIdx].EthereumKey.PrivateKey}, - Cmd: []string{ - "peggo", - "bridge", - "send-to-cosmos", - s.GravityContractAddr, - tokenAddr, - toUmeeAddr, - amount, - "--eth-rpc", - fmt.Sprintf("http://%s:8545", s.EthResource.Container.Name[1:]), - "--cosmos-chain-id", - s.Chain.ID, - "--cosmos-grpc", - fmt.Sprintf("tcp://%s:9090", s.ValResources[valIdx].Container.Name[1:]), - "--tendermint-rpc", - fmt.Sprintf("http://%s:26657", s.ValResources[valIdx].Container.Name[1:]), - }, - }) - s.Require().NoError(err) - - var ( - outBuf bytes.Buffer - errBuf bytes.Buffer - ) - - err = s.DkrPool.Client.StartExec(exec.ID, docker.StartExecOptions{ - Context: ctx, - Detach: false, - OutputStream: &outBuf, - ErrorStream: &errBuf, - }) - s.Require().NoErrorf(err, "stdout: %s, stderr: %s", outBuf.String(), errBuf.String()) - - re := regexp.MustCompile(`Transaction: (0x.+)`) - tokens := re.FindStringSubmatch(errBuf.String()) - s.Require().Len(tokens, 2) - - txHash := tokens[1] - s.Require().NotEmpty(txHash) - - s.Require().Eventuallyf( - func() bool { - return s.QueryEthTx(ctx, s.EthClient, txHash) == nil - }, - 5*time.Minute, - 5*time.Second, - "stdout: %s, stderr: %s", - outBuf.String(), errBuf.String(), - ) -} - func (s *E2ETestSuite) SendIBC(srcChainID, dstChainID, recipient string, token sdk.Coin) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -517,53 +188,6 @@ func (s *E2ETestSuite) QueryUmeeDenomBalance(endpoint, addr, denom string) (sdk. return *resp.Balance, nil } -func (s *E2ETestSuite) QueryDenomToERC20(endpoint, denom string) (string, bool, error) { - endpoint = fmt.Sprintf("%s/gravity/v1beta/cosmos_originated/denom_to_erc20?denom=%s", endpoint, denom) - var resp gravitytypes.QueryDenomToERC20Response - if err := s.QueryREST(endpoint, &resp); err != nil { - return "", false, err - } - - return resp.Erc20, resp.CosmosOriginated, nil -} - -func (s *E2ETestSuite) QueryEthTx(ctx context.Context, c *ethclient.Client, txHash string) error { - _, pending, err := c.TransactionByHash(ctx, ethcmn.HexToHash(txHash)) - if err != nil { - return err - } - if pending { - return fmt.Errorf("ethereum tx %s is still pending", txHash) - } - - return nil -} - -func (s *E2ETestSuite) QueryEthTokenBalance(ctx context.Context, c *ethclient.Client, contractAddr, recipientAddr string) (int64, error) { - data, err := EthABI.Pack(AbiMethodNameBalanceOf, ethcmn.HexToAddress(recipientAddr)) - if err != nil { - return 0, fmt.Errorf("failed to pack ABI method call: %w", err) - } - - token := ethcmn.HexToAddress(contractAddr) - callMsg := ethereum.CallMsg{ - To: &token, - Data: data, - } - - bz, err := c.CallContract(ctx, callMsg, nil) - if err != nil { - return 0, fmt.Errorf("failed to call Ethereum contract: %w", err) - } - - balance, err := strconv.ParseInt(ethcmn.Bytes2Hex(bz), 16, 32) - if err != nil { - return 0, fmt.Errorf("failed to parse balance: %w", err) - } - - return balance, nil -} - func (s *E2ETestSuite) QueryUmeeBalance( umeeValIdx int, umeeTokenDenom string, @@ -583,28 +207,6 @@ func (s *E2ETestSuite) QueryUmeeBalance( return umeeBalance, umeeAddr } -func (s *E2ETestSuite) QueryUmeeEthBalance( - umeeValIdx, - orchestratorIdx int, - umeeTokenDenom, - ethTokenAddr string, -) (umeeBalance sdk.Coin, ethBalance int64, umeeAddr, ethAddr string) { - umeeBalance, umeeAddr = s.QueryUmeeBalance(umeeValIdx, umeeTokenDenom) - - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - ethAddr = s.Chain.Orchestrators[orchestratorIdx].EthereumKey.Address - - ethBalance, err := s.QueryEthTokenBalance(ctx, s.EthClient, ethTokenAddr, ethAddr) - s.Require().NoError(err) - s.T().Logf( - "ETh Balance of tokens; index: %d, addr: %s, amount: %d, denom: %s, erc20Addr: %s", - orchestratorIdx, ethAddr, ethBalance, umeeTokenDenom, ethTokenAddr, - ) - - return umeeBalance, ethBalance, umeeAddr, ethAddr -} - func decodeTx(cdc codec.Codec, txBytes []byte) (*sdktx.Tx, error) { var raw sdktx.TxRaw diff --git a/tests/e2e/suite.go b/tests/e2e/suite.go deleted file mode 100644 index 48a7a5516b..0000000000 --- a/tests/e2e/suite.go +++ /dev/null @@ -1,20 +0,0 @@ -package e2e - -// type IntegrationTestSuite struct { -// suite.Suite - -// tmpDirs []string -// chain *setup.chain -// ethClient *ethclient.Client -// gaiaRPC *rpchttp.HTTP -// dkrPool *dockertest.Pool -// dkrNet *dockertest.Network -// ethResource *dockertest.Resource -// gaiaResource *dockertest.Resource -// hermesResource *dockertest.Resource -// priceFeederResource *dockertest.Resource -// valResources []*dockertest.Resource -// orchResources []*dockertest.Resource -// gravityContractAddr string -// umee client.Client -// } diff --git a/tests/util/ethereum.go b/tests/util/ethereum.go deleted file mode 100644 index fde90d0d49..0000000000 --- a/tests/util/ethereum.go +++ /dev/null @@ -1,34 +0,0 @@ -package util - -import ( - "crypto/ecdsa" - crand "crypto/rand" - "fmt" - "io" - - ethcmn "github.com/ethereum/go-ethereum/common" - ethcrypto "github.com/ethereum/go-ethereum/crypto" -) - -// GenerateRandomEthKey generates a random Ethereum key pair. -func GenerateRandomEthKey() (*ecdsa.PrivateKey, *ecdsa.PublicKey, ethcmn.Address, error) { - return GenerateRandomEthKeyFromRand(crand.Reader) -} - -// GenerateRandomEthKeyFromRand generates a random Ethereum key pair from a -// reader. -func GenerateRandomEthKeyFromRand(r io.Reader) (*ecdsa.PrivateKey, *ecdsa.PublicKey, ethcmn.Address, error) { - privKey, err := ecdsa.GenerateKey(ethcrypto.S256(), r) - if err != nil { - return nil, nil, ethcmn.Address{}, err - } - - publicKey := privKey.Public() - publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) - if !ok { - err := fmt.Errorf("unexpected public key type; expected: %T, got: %T", &ecdsa.PublicKey{}, publicKey) - return nil, nil, ethcmn.Address{}, err - } - - return privKey, publicKeyECDSA, ethcrypto.PubkeyToAddress(*publicKeyECDSA), nil -} diff --git a/util/keys/keys.go b/util/keys/keys.go index 64ce7a2fda..8a0a2861ec 100644 --- a/util/keys/keys.go +++ b/util/keys/keys.go @@ -57,3 +57,14 @@ func ExtractAddressAndString(startIndex int, key []byte) (addr sdk.AccAddress, s s, nextIndex, err = ExtractString(nextIndex, key) return addr, s, nextIndex, err } + +// ToStr takes the full key and converts it to a string +func ToStr(key []byte) string { + return string(key) +} + +// NoLastByte returns sub-slice of the key without the last byte. +// Panics if length of key is zero. +func NoLastByte(key []byte) string { + return string(key[:len(key)-1]) +} diff --git a/util/store/iter.go b/util/store/iter.go index ba6e2072e2..441f3951d3 100644 --- a/util/store/iter.go +++ b/util/store/iter.go @@ -80,9 +80,3 @@ func SumCoins(s storetypes.KVStore, f StrExtractor) sdk.Coins { // StrExtractor is a function type which will take a bytes string value and extracts // string out of it. type StrExtractor func([]byte) string - -// NoLastByte returns sub-slice of the key without the last byte. -// Panics if length of key is zero. -func NoLastByte(key []byte) string { - return string(key[:len(key)-1]) -} diff --git a/util/store/iter_test.go b/util/store/iter_test.go index 7322d41956..b61ecdd7d3 100644 --- a/util/store/iter_test.go +++ b/util/store/iter_test.go @@ -8,6 +8,7 @@ import ( "gotest.tools/v3/assert" "github.com/umee-network/umee/v5/tests/tsdk" + "github.com/umee-network/umee/v5/util/keys" ) func TestIterate(t *testing.T) { @@ -74,7 +75,7 @@ func TestSumCoins(t *testing.T) { } pdb := prefixstore.NewStore(db, []byte(prefix)) - sum := SumCoins(pdb, NoLastByte) + sum := SumCoins(pdb, keys.NoLastByte) sum.Sort() assert.DeepEqual(t, expected, sum) } diff --git a/x/incentive/keeper/iter.go b/x/incentive/keeper/iter.go index 62c875a686..27f3e0825d 100644 --- a/x/incentive/keeper/iter.go +++ b/x/incentive/keeper/iter.go @@ -87,10 +87,10 @@ func (k Keeper) getAllAccountUnbondings(ctx sdk.Context) ([]incentive.AccountUnb // getAllTotalUnbonding gets total unbonding for all uTokens (used for a query) func (k Keeper) getAllTotalUnbonding(ctx sdk.Context) sdk.Coins { - return store.SumCoins(k.prefixStore(ctx, keyPrefixTotalUnbonding), store.NoLastByte) + return store.SumCoins(k.prefixStore(ctx, keyPrefixTotalUnbonding), keys.ToStr) } // getAllTotalBonded gets total bonded for all uTokens (used for a query) func (k Keeper) getAllTotalBonded(ctx sdk.Context) sdk.Coins { - return store.SumCoins(k.prefixStore(ctx, keyPrefixTotalBonded), store.NoLastByte) + return store.SumCoins(k.prefixStore(ctx, keyPrefixTotalBonded), keys.ToStr) } diff --git a/x/incentive/keeper/keys.go b/x/incentive/keeper/keys.go index c740fc3197..3a0d7bd724 100644 --- a/x/incentive/keeper/keys.go +++ b/x/incentive/keeper/keys.go @@ -48,14 +48,14 @@ func keyIncentiveProgram(id uint32, status incentive.ProgramStatus) []byte { // keyTotalBonded returns a KVStore key for total bonded uTokens of a given denom. func keyTotalBonded(denom string) []byte { - // totalBondedPrefix | denom | 0x00 - return util.ConcatBytes(1, keyPrefixTotalBonded, []byte(denom)) + // totalBondedPrefix | denom + return util.ConcatBytes(0, keyPrefixTotalBonded, []byte(denom)) } // keyTotalUnbonding returns a KVStore key for total unbonding uTokens of a given denom. func keyTotalUnbonding(denom string) []byte { - // totalUnbondingPrefix | denom | 0x00 - return util.ConcatBytes(1, keyPrefixTotalUnbonding, []byte(denom)) + // totalUnbondingPrefix | denom + return util.ConcatBytes(0, keyPrefixTotalUnbonding, []byte(denom)) } // keyBondAmount returns a KVStore key for bonded amounts for a uToken denom and account. diff --git a/x/leverage/README.md b/x/leverage/README.md index 7b59cc6dc4..dfc97a437f 100644 --- a/x/leverage/README.md +++ b/x/leverage/README.md @@ -20,6 +20,7 @@ The leverage module depends directly on `x/oracle` for asset prices, and interac - [uToken Exchange Rate](#utoken-exchange-rate) - [Supply Utilization](#supply-utilization) - [Borrow Limit](#borrow-limit) + - [Borrow Factor](#borrow-factor) - [Liquidation Threshold](#liquidation-threshold) - [Borrow APY](#borrow-apy) - [Supplying APY](#supplying-apy) @@ -168,6 +169,21 @@ A user's borrow limit is the sum of the contributions from each denomination of For tokens with hith historic prices enabled (indicated by a `HistoricMedians` parameter greater than zero), each collateral `TokenValue` is computed with `PriceModeLow`, i.e. the lower of either spot price or historic price is used. +#### Borrow Factor + +Each token in the `Token Registry` has a parameter called `CollateralWeight`, always less than 1, which determines the portion of the token's value that goes towards a user's borrow limit, when the token is used as collateral. + +An implied parameter `BorrowFactor` is derived from `CollateralWeight` - specifically, it is the minimum of `2.0` and `1/CollateralWeight`. +The maximum borrow factor of `2.0` allows risky or non-collateral assets (`0 <= CollateralWeight < 0.5`) to be borrowed to a certain minimum degree. + +When a user is borrowing, their borrow limit is whichever is more restrictive of the following two rules: + +- Borrowed value must be less than collateral value times `CollateralWeight` (sum over each collateral asset) +- Borrowed value times `BorrowFactor` (sum over each borrowed asset) must be less than collateral value. + +This means that when the original borrow limit based on collateral weight would allow a higher quality collateral to borrow a risky asset with a small margin of safety, the user's effective collateral weight is reduced to that of the riskier asset. +(Or `0.5` at the minimum.) + #### Historic Borrow Limit, Value The leverage module also makes use of the oracle's historic prices to enforce an additional restriction on borrowing. diff --git a/x/leverage/client/cli/tx.go b/x/leverage/client/cli/tx.go index 4eeb43767a..d7f6c372a3 100644 --- a/x/leverage/client/cli/tx.go +++ b/x/leverage/client/cli/tx.go @@ -322,7 +322,7 @@ $ umeed tx leverage liquidate %s 50000000uumee u/uumee --from mykey`, func GetCmdLeveragedLiquidate() *cobra.Command { cmd := &cobra.Command{ Use: "lev-liquidate [borrower] [repay-denom] [reward-denom]", - Args: cobra.ExactArgs(3), + Args: cobra.RangeArgs(1, 3), Short: "Liquidates by moving borrower debt to the liquidator and immediately collateralizes the reward.", Long: strings.TrimSpace( fmt.Sprintf(` @@ -349,8 +349,14 @@ $ umeed tx leverage lev-liquidate %s uumee uumee --from mykey`, return err } - repayDenom := args[1] - rewardDenom := args[2] + var repayDenom, rewardDenom string + if len(args) > 1 { + repayDenom = args[1] + } + + if len(args) > 2 { + rewardDenom = args[2] + } msg := types.NewMsgLeveragedLiquidate(clientCtx.GetFromAddress(), borrowerAddr, repayDenom, rewardDenom) if err = msg.ValidateBasic(); err != nil { diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index de73228f12..dcf3419654 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -8,7 +8,8 @@ import ( ) // assertBorrowerHealth returns an error if a borrower is currently above their borrow limit, -// under either recent (historic median) or current prices. It returns an error if +// under either recent (historic median) or current prices. Checks using borrow limit based +// on collateral weight, then check separately for borrow limit using borrow factor. Error if // borrowed asset prices cannot be calculated, but will try to treat collateral whose prices are // unavailable as having zero value. This can still result in a borrow limit being too low, // unless the remaining collateral is enough to cover all borrows. @@ -21,18 +22,34 @@ func (k Keeper) assertBorrowerHealth(ctx sdk.Context, borrowerAddr sdk.AccAddres borrowed := k.GetBorrowerBorrows(ctx, borrowerAddr) collateral := k.GetBorrowerCollateral(ctx, borrowerAddr) - value, err := k.TotalTokenValue(ctx, borrowed, types.PriceModeHigh) + // check health using collateral weight + borrowValue, err := k.TotalTokenValue(ctx, borrowed, types.PriceModeHigh) if err != nil { return err } - limit, err := k.VisibleBorrowLimit(ctx, collateral) + borrowLimit, err := k.VisibleBorrowLimit(ctx, collateral) if err != nil { return err } - if value.GT(limit.Mul(maxUsage)) { + if borrowValue.GT(borrowLimit.Mul(maxUsage)) { return types.ErrUndercollaterized.Wrapf( - "borrowed: %s, limit: %s, max usage %s", value, limit, maxUsage) + "borrowed: %s, limit: %s, max usage %s", borrowValue, borrowLimit, maxUsage) } + + // check health using borrow factor + weightedBorrowValue, err := k.ValueWithBorrowFactor(ctx, borrowed, types.PriceModeHigh) + if err != nil { + return err + } + collateralValue, err := k.VisibleUTokensValue(ctx, collateral, types.PriceModeLow) + if err != nil { + return err + } + if weightedBorrowValue.GT(collateralValue.Mul(maxUsage)) { + return types.ErrUndercollaterized.Wrapf( + "weighted borrow: %s, collateral value: %s, max usage %s", weightedBorrowValue, collateralValue, maxUsage) + } + return nil } diff --git a/x/leverage/keeper/collateral.go b/x/leverage/keeper/collateral.go index 018bf7b52f..200fc78494 100644 --- a/x/leverage/keeper/collateral.go +++ b/x/leverage/keeper/collateral.go @@ -73,9 +73,9 @@ func (k Keeper) GetTotalCollateral(ctx sdk.Context, denom string) sdk.Coin { } // CalculateCollateralValue uses the price oracle to determine the value (in USD) provided by -// collateral sdk.Coins, using each token's uToken exchange rate. Always uses spot price. +// collateral sdk.Coins, using each token's uToken exchange rate. // An error is returned if any input coins are not uTokens or if value calculation fails. -func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins) (sdk.Dec, error) { +func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { total := sdk.ZeroDec() for _, coin := range collateral { @@ -86,12 +86,12 @@ func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins) } // get USD value of base assets - v, err := k.TokenValue(ctx, baseAsset, types.PriceModeSpot) + v, err := k.TokenValue(ctx, baseAsset, mode) if err != nil { return sdk.ZeroDec(), err } - // add each collateral coin's weighted value to borrow limit + // add each collateral coin's value to borrow limit total = total.Add(v) } @@ -99,10 +99,10 @@ func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins) } // VisibleCollateralValue uses the price oracle to determine the value (in USD) provided by -// collateral sdk.Coins, using each token's uToken exchange rate. Always uses spot price. +// collateral sdk.Coins, using each token's uToken exchange rate. // Unlike CalculateCollateralValue, this function will not return an error if value calculation // fails on a token - instead, that token will contribute zero value to the total. -func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins) (sdk.Dec, error) { +func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { total := sdk.ZeroDec() for _, coin := range collateral { @@ -113,7 +113,7 @@ func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins) (s } // get USD value of base assets - v, err := k.TokenValue(ctx, baseAsset, types.PriceModeSpot) + v, err := k.TokenValue(ctx, baseAsset, mode) if err == nil { // for coins that did not error, add their value to the total total = total.Add(v) @@ -169,13 +169,13 @@ func (k *Keeper) VisibleCollateralShare(ctx sdk.Context, denom string) (sdk.Dec, thisCollateral := sdk.NewCoins(sdk.NewCoin(denom, systemCollateral.AmountOf(denom))) // get USD collateral value for all uTokens combined, except those experiencing price outages - totalValue, err := k.VisibleCollateralValue(ctx, systemCollateral) + totalValue, err := k.VisibleCollateralValue(ctx, systemCollateral, types.PriceModeSpot) if err != nil { return sdk.ZeroDec(), err } // get USD collateral value for this uToken only - thisValue, err := k.CalculateCollateralValue(ctx, thisCollateral) + thisValue, err := k.CalculateCollateralValue(ctx, thisCollateral, types.PriceModeSpot) if err != nil { return sdk.ZeroDec(), err } diff --git a/x/leverage/keeper/grpc_query.go b/x/leverage/keeper/grpc_query.go index 3d63a4c639..16ad0d4621 100644 --- a/x/leverage/keeper/grpc_query.go +++ b/x/leverage/keeper/grpc_query.go @@ -228,7 +228,7 @@ func (q Querier) AccountSummary( } // collateral value always uses spot prices, and this line skips assets that are missing prices - collateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral) + collateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral, types.PriceModeSpot) if err != nil { return nil, err } diff --git a/x/leverage/keeper/grpc_query_test.go b/x/leverage/keeper/grpc_query_test.go index 28119e7db7..6c984c7cba 100644 --- a/x/leverage/keeper/grpc_query_test.go +++ b/x/leverage/keeper/grpc_query_test.go @@ -25,7 +25,7 @@ func (s *IntegrationTestSuite) TestQuerier_RegisteredTokens() { "valid: get the all registered tokens", "", types.QueryRegisteredTokens{}, - 5, + 6, }, { "valid: get the registered token info by base_denom", diff --git a/x/leverage/keeper/inspector.go b/x/leverage/keeper/inspector.go index d3058e6792..5a5f113f5f 100644 --- a/x/leverage/keeper/inspector.go +++ b/x/leverage/keeper/inspector.go @@ -68,7 +68,7 @@ func (q Querier) Inspect( borrowed := k.GetBorrowerBorrows(ctx, addr) borrowedValue, _ := k.TotalTokenValue(ctx, borrowed, types.PriceModeSpot) collateral := k.GetBorrowerCollateral(ctx, addr) - collateralValue, _ := k.CalculateCollateralValue(ctx, collateral) + collateralValue, _ := k.CalculateCollateralValue(ctx, collateral, types.PriceModeSpot) liquidationThreshold, _ := k.CalculateLiquidationThreshold(ctx, collateral) account := types.InspectAccount{ diff --git a/x/leverage/keeper/inspector_test.go b/x/leverage/keeper/inspector_test.go index e9bf57df06..e13d2c5a05 100644 --- a/x/leverage/keeper/inspector_test.go +++ b/x/leverage/keeper/inspector_test.go @@ -25,13 +25,17 @@ func TestNeat(t *testing.T) { "-12.555": -12.55, // truncates default to cent "-0.00123456789": -0.001234, // truncates <0.01 to millionth "-0.000000987654321": -0.000000987654321, // <0.000001 gets maximum precision - // edge case: >2^64 displays incorrectly - // this should be fine, since this is a display-only function (not used in transactions) - // which is used on dollar (not token) amounts - "123456789123456789123456789.123456789": -9.223372036854776e+21, } for s, f := range cases { assert.Equal(f, neat(sdk.MustNewDecFromStr(s))) } + + // edge case: >2^64 displays incorrectly + // this should be fine, since this is a display-only function (not used in transactions) + // which is used on dollar (not token) amounts + assert.NotEqual( + 123456789123456789123456789.123456789, + neat(sdk.MustNewDecFromStr("123456789123456789123456789.123456789")), + ) } diff --git a/x/leverage/keeper/iter.go b/x/leverage/keeper/iter.go index 97337592cc..0795a3b0d1 100644 --- a/x/leverage/keeper/iter.go +++ b/x/leverage/keeper/iter.go @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/umee-network/umee/v5/util" + "github.com/umee-network/umee/v5/util/keys" "github.com/umee-network/umee/v5/util/store" "github.com/umee-network/umee/v5/x/leverage/types" ) @@ -177,12 +178,12 @@ func (k Keeper) SweepBadDebts(ctx sdk.Context) error { // GetAllUTokenSupply returns total supply of all uToken denoms. func (k Keeper) GetAllUTokenSupply(ctx sdk.Context) sdk.Coins { - return store.SumCoins(k.prefixStore(ctx, types.KeyPrefixUtokenSupply), store.NoLastByte) + return store.SumCoins(k.prefixStore(ctx, types.KeyPrefixUtokenSupply), keys.NoLastByte) } // GetAllReserves returns all reserves. func (k Keeper) GetAllReserves(ctx sdk.Context) sdk.Coins { - return store.SumCoins(k.prefixStore(ctx, types.KeyPrefixReserveAmount), store.NoLastByte) + return store.SumCoins(k.prefixStore(ctx, types.KeyPrefixReserveAmount), keys.NoLastByte) } func (k Keeper) prefixStore(ctx sdk.Context, p []byte) storetypes.KVStore { diff --git a/x/leverage/keeper/keeper.go b/x/leverage/keeper/keeper.go index b11563e5c2..19de4d66fe 100644 --- a/x/leverage/keeper/keeper.go +++ b/x/leverage/keeper/keeper.go @@ -372,6 +372,21 @@ func (k Keeper) Liquidate( func (k Keeper) LeveragedLiquidate( ctx sdk.Context, liquidatorAddr, borrowerAddr sdk.AccAddress, repayDenom, rewardDenom string, ) (repaid sdk.Coin, reward sdk.Coin, err error) { + // If the message did not specify repay or reward denoms, select one arbitrarily (first in + // denom alphabetical order) from borrower position. Then proceed normally with the transaction. + if repayDenom == "" { + borrowed := k.GetBorrowerBorrows(ctx, borrowerAddr) + if !borrowed.IsZero() { + repayDenom = borrowed[0].Denom + } + } + if rewardDenom == "" { + collateral := k.GetBorrowerCollateral(ctx, borrowerAddr) + if !collateral.IsZero() { + rewardDenom = types.ToTokenDenom(collateral[0].Denom) + } + } + if err := k.validateAcceptedDenom(ctx, repayDenom); err != nil { return sdk.Coin{}, sdk.Coin{}, err } diff --git a/x/leverage/keeper/limits.go b/x/leverage/keeper/limits.go index 5b0e28185e..16cb9030ba 100644 --- a/x/leverage/keeper/limits.go +++ b/x/leverage/keeper/limits.go @@ -7,9 +7,9 @@ import ( ) // userMaxWithdraw calculates the maximum amount of uTokens an account can currently withdraw and the amount of -// these uTokens is non-collateral. Input denom should be a base token. If oracle prices are missing for some of the -// borrower's collateral (other than the denom being withdrawn), computes the maximum safe withdraw allowed by only -// the collateral whose prices are known. +// these uTokens which are non-collateral. Input denom should be a base token. If oracle prices are missing for +// some of the borrower's collateral (other than the denom being withdrawn), computes the maximum safe withdraw +// allowed by only the collateral whose prices are known. func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom string) (sdk.Coin, sdk.Coin, error) { uDenom := types.ToUTokenDenom(denom) availableTokens := sdk.NewCoin(denom, k.AvailableLiquidity(ctx, denom)) @@ -37,6 +37,27 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil } + // calculate collateral value for the account, using the lower of spot or historic prices for each token + // will count collateral with missing prices as zero value without returning an error + collateralValue, err := k.VisibleCollateralValue(ctx, totalCollateral, types.PriceModeLow) + if err != nil { + // for errors besides a missing price, the whole transaction fails + return sdk.Coin{}, sdk.Coin{}, err + } + + // calculate weighted borrowed value - used by the borrow factor limit + weightedBorrowValue, err := k.ValueWithBorrowFactor(ctx, totalBorrowed, types.PriceModeHigh) + if nonOracleError(err) { + // for errors besides a missing price, the whole transaction fails + return sdk.Coin{}, sdk.Coin{}, err + } + if err != nil { + // for missing prices on borrowed assets, we can't withdraw any collateral + // but can withdraw non-collateral uTokens + withdrawAmount := sdk.MinInt(walletUtokens, availableUTokens.Amount) + return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil + } + // if no non-blacklisted tokens are borrowed, withdraw the maximum available amount if borrowedValue.IsZero() { withdrawAmount := walletUtokens.Add(unbondedCollateral.Amount) @@ -46,15 +67,24 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str // compute the borrower's borrow limit using all their collateral // except the denom being withdrawn (also excluding collateral missing oracle prices) - otherBorrowLimit, err := k.VisibleBorrowLimit(ctx, otherCollateral) + otherCollateralBorrowLimit, err := k.VisibleBorrowLimit(ctx, otherCollateral) if err != nil { return sdk.Coin{}, sdk.Coin{}, err } // if their other collateral fully covers all borrows, withdraw the maximum available amount - if borrowedValue.LT(otherBorrowLimit) { - withdrawAmount := walletUtokens.Add(unbondedCollateral.Amount) - withdrawAmount = sdk.MinInt(withdrawAmount, availableUTokens.Amount) - return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil + if borrowedValue.LT(otherCollateralBorrowLimit) { + // also check collateral value vs weighted borrow (borrow factor limit) + otherCollateralValue, err := k.VisibleCollateralValue(ctx, otherCollateral, types.PriceModeLow) + if err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + // if weighted borrow does not exceed other collateral value, this collateral can be fully withdrawn + if otherCollateralValue.GTE(weightedBorrowValue) { + // in this case, both borrow limits will not be exceeded even if all collateral is withdrawn + withdrawAmount := walletUtokens.Add(unbondedCollateral.Amount) + withdrawAmount = sdk.MinInt(withdrawAmount, availableUTokens.Amount) + return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil + } } // for nonzero borrows, calculations are based on unused borrow limit @@ -64,8 +94,8 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str if err != nil { return sdk.Coin{}, sdk.Coin{}, err } - // borrowers above their borrow limit cannot withdraw collateral, but can withdraw wallet uTokens - if borrowLimit.LTE(borrowedValue) { + // borrowers above either of their borrow limits cannot withdraw collateral, but can withdraw wallet uTokens + if borrowLimit.LTE(borrowedValue) || collateralValue.LTE(weightedBorrowValue) { withdrawAmount := sdk.MinInt(walletUtokens, availableUTokens.Amount) return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil } @@ -81,8 +111,19 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str return sdk.Coin{}, sdk.Coin{}, err } - // if only a portion of collateral is unused, withdraw only that portion + // if only a portion of collateral is unused, withdraw only that portion (regular borrow limit) unusedCollateralFraction := unusedBorrowLimit.Quo(specificBorrowLimit) + + // calculate value of this collateral specifically, which is used in borrow factor's borrow limit + specificCollateralValue, err := k.CalculateCollateralValue(ctx, sdk.NewCoins(thisCollateral), types.PriceModeLow) + if err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + unusedCollateralValue := collateralValue.Sub(weightedBorrowValue) + // Find the more restrictive of either borrow factor limit or borrow limit + unusedCollateralFraction = sdk.MinDec(unusedCollateralFraction, unusedCollateralValue.Quo(specificCollateralValue)) + + // Both borrow limits are satisfied by this withdrawal amount. The restrictions below relate to neither. unusedCollateral := unusedCollateralFraction.MulInt(thisCollateral.Amount).TruncateInt() // find the minimum of unused collateral (due to borrows) or unbonded collateral (incentive module) @@ -101,11 +142,16 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str // userMaxBorrow calculates the maximum amount of a given token an account can currently borrow. // input denom should be a base token. If oracle prices are missing for some of the borrower's -// collateral, computes the maximum safe borrow allowed by only the collateral whose prices are known +// collateral, computes the maximum safe borrow allowed by only the collateral whose prices are known. func (k *Keeper) userMaxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom string) (sdk.Coin, error) { if types.HasUTokenPrefix(denom) { return sdk.Coin{}, types.ErrUToken } + token, err := k.GetTokenSettings(ctx, denom) + if err != nil { + return sdk.Coin{}, err + } + availableTokens := k.AvailableLiquidity(ctx, denom) totalBorrowed := k.GetBorrowerBorrows(ctx, addr) @@ -122,6 +168,17 @@ func (k *Keeper) userMaxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom strin return sdk.NewCoin(denom, sdk.ZeroInt()), nil } + // calculate weighted borrowed value for the account, using the higher of spot or historic prices + weightedBorrowedValue, err := k.ValueWithBorrowFactor(ctx, totalBorrowed, types.PriceModeHigh) + if nonOracleError(err) { + // non-oracle errors fail the transaction (or query) + return sdk.Coin{}, err + } + if err != nil { + // oracle errors cause max borrow to be zero + return sdk.NewCoin(denom, sdk.ZeroInt()), nil + } + // calculate borrow limit for the account, using only collateral whose price is known borrowLimit, err := k.VisibleBorrowLimit(ctx, totalCollateral) if err != nil { @@ -132,11 +189,27 @@ func (k *Keeper) userMaxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom strin return sdk.NewCoin(denom, sdk.ZeroInt()), nil } + // calculate collateral value limit for the account, using only collateral whose price is known + collateralValue, err := k.VisibleCollateralValue(ctx, totalCollateral, types.PriceModeLow) + if err != nil { + return sdk.Coin{}, err + } + // borrowers above their borrow factor borrow limit cannot borrow + if collateralValue.LTE(weightedBorrowedValue) { + return sdk.NewCoin(denom, sdk.ZeroInt()), nil + } + // determine the USD amount of borrow limit that is currently unused unusedBorrowLimit := borrowLimit.Sub(borrowedValue) + // determine the USD amount that can be borrowed according to borrow factor limit + maxBorrowValueIncrease := collateralValue.Sub(weightedBorrowedValue).Quo(token.BorrowFactor()) + + // finds the most restrictive of regular borrow limit and borrow factor limit + valueToBorrow := sdk.MinDec(unusedBorrowLimit, maxBorrowValueIncrease) + // determine max borrow, using the higher of spot or historic prices for the token to borrow - maxBorrow, err := k.TokenWithValue(ctx, denom, unusedBorrowLimit, types.PriceModeHigh) + maxBorrow, err := k.TokenWithValue(ctx, denom, valueToBorrow, types.PriceModeHigh) if nonOracleError(err) { // non-oracle errors fail the transaction (or query) return sdk.Coin{}, err @@ -177,7 +250,11 @@ func (k *Keeper) maxCollateralFromShare(ctx sdk.Context, denom string) (sdkmath. thisDenomCollateral := sdk.NewCoin(denom, systemCollateral.AmountOf(denom)) // get USD collateral value for all other denoms, skipping those which are missing oracle prices - otherDenomsValue, err := k.VisibleCollateralValue(ctx, systemCollateral.Sub(thisDenomCollateral)) + otherDenomsValue, err := k.VisibleCollateralValue( + ctx, + systemCollateral.Sub(thisDenomCollateral), + types.PriceModeSpot, + ) if err != nil { return sdk.ZeroInt(), err } diff --git a/x/leverage/keeper/liquidate.go b/x/leverage/keeper/liquidate.go index 54100681e7..c346fec0e5 100644 --- a/x/leverage/keeper/liquidate.go +++ b/x/leverage/keeper/liquidate.go @@ -33,7 +33,7 @@ func (k Keeper) getLiquidationAmounts( if err != nil { return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err } - collateralValue, err := k.CalculateCollateralValue(ctx, borrowerCollateral) + collateralValue, err := k.CalculateCollateralValue(ctx, borrowerCollateral, types.PriceModeSpot) if err != nil { return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err } diff --git a/x/leverage/keeper/msg_server_test.go b/x/leverage/keeper/msg_server_test.go index 628a4b343c..62931a5b46 100644 --- a/x/leverage/keeper/msg_server_test.go +++ b/x/leverage/keeper/msg_server_test.go @@ -98,7 +98,7 @@ func (s *IntegrationTestSuite) TestAddTokensToRegistry() { s.Require().NoError(err) // no tokens should have been deleted tokens := s.app.LeverageKeeper.GetAllRegisteredTokens(s.ctx) - s.Require().Len(tokens, 6) + s.Require().Len(tokens, 7) token, err := s.app.LeverageKeeper.GetTokenSettings(s.ctx, ntA.BaseDenom) s.Require().NoError(err) @@ -170,7 +170,7 @@ func (s *IntegrationTestSuite) TestUpdateRegistry() { s.Require().NoError(err) // no tokens should have been deleted tokens := s.app.LeverageKeeper.GetAllRegisteredTokens(s.ctx) - s.Require().Len(tokens, 5) + s.Require().Len(tokens, 6) token, err := s.app.LeverageKeeper.GetTokenSettings(s.ctx, "uumee") s.Require().NoError(err) @@ -350,6 +350,13 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { // borrowed value is $10 (current) or $5 (historic) // collateral weights are always 0.25 in testing + // create an UMEE borrower using STABLE collateral + stableUmeeBorrower := s.newAccount(coin.New(stableDenom, 100_000000)) + s.supply(stableUmeeBorrower, coin.New(stableDenom, 100_000000)) + s.collateralize(stableUmeeBorrower, coin.New("u/"+stableDenom, 100_000000)) + s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) + // UMEE and STABLE have the same price but different collateral weights + tcs := []struct { msg string addr sdk.AccAddress @@ -455,6 +462,14 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { nil, sdk.Coin{}, types.ErrUndercollaterized, + }, { + "borrow limit (undercollateralized due to borrow factor but not collateral weight)", + stableUmeeBorrower, + coin.New("u/"+stableDenom, 50_000000), + nil, + nil, + sdk.Coin{}, + types.ErrUndercollaterized, }, } @@ -555,6 +570,13 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { // borrowed value is $10 (current) or $5 (historic) // collateral weights are always 0.25 in testing + // create an UMEE borrower using STABLE collateral + stableUmeeBorrower := s.newAccount(coin.New(stableDenom, 100_000000)) + s.supply(stableUmeeBorrower, coin.New(stableDenom, 100_000000)) + s.collateralize(stableUmeeBorrower, coin.New("u/"+stableDenom, 100_000000)) + s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) + // UMEE and STABLE have the same price but different collateral weights + zeroUmee := coin.Zero(umeeDenom) zeroUUmee := coin.New("u/"+umeeDenom, 0) tcs := []struct { @@ -574,7 +596,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { sdk.Coin{}, sdk.Coin{}, types.ErrNotRegisteredToken, - }, { + }, + { "can't borrow uToken", supplier, "u/" + umeeDenom, @@ -582,7 +605,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { sdk.Coin{}, sdk.Coin{}, types.ErrUToken, - }, { + }, + { "max withdraw umee", supplier, umeeDenom, @@ -590,7 +614,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New("u/"+umeeDenom, 75_000000), coin.New(umeeDenom, 100_000000), nil, - }, { + }, + { "duplicate max withdraw umee", supplier, umeeDenom, @@ -598,7 +623,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { zeroUUmee, zeroUmee, nil, - }, { + }, + { "max withdraw with borrow", other, umeeDenom, @@ -606,7 +632,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New("u/"+umeeDenom, 60_000000), coin.New(umeeDenom, 60_000000), nil, - }, { + }, + { "max withdrawal (dump borrower)", dumpborrower, pumpDenom, @@ -614,7 +641,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New("u/"+pumpDenom, 20_000000), coin.New(pumpDenom, 20_000000), nil, - }, { + }, + { "max withdrawal (pump borrower)", pumpborrower, dumpDenom, @@ -623,6 +651,15 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New(dumpDenom, 20_000000), nil, }, + { + "max withdrawal (borrow factor 2 with stablecoin collateral)", + stableUmeeBorrower, + stableDenom, + coin.New("u/"+stableDenom, 40_000000), + coin.New("u/"+stableDenom, 40_000000), + coin.New(stableDenom, 40_000000), + nil, + }, } for _, tc := range tcs { @@ -1273,6 +1310,13 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { // collateral value is $50 (current) or $100 (historic) // collateral weights are always 0.25 in testing + // create an UMEE borrower using STABLE collateral + stableUmeeBorrower := s.newAccount(coin.New(stableDenom, 40_000000)) + s.supply(stableUmeeBorrower, coin.New(stableDenom, 40_000000)) + s.collateralize(stableUmeeBorrower, coin.New("u/"+stableDenom, 40_000000)) + s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 3_000000)) + // UMEE and STABLE have the same price but different collateral weights + tcs := []testCase{ { "uToken", @@ -1295,14 +1339,19 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { coin.New(umeeDenom, 70_000000), nil, }, { - "additional borrow", - borrower, - coin.New(umeeDenom, 20_000000), + "stable umee borrower (acceptable)", + stableUmeeBorrower, + coin.New(umeeDenom, 17_000000), nil, + }, { + "stable umee borrower (borrow factor limit)", + stableUmeeBorrower, + coin.New(umeeDenom, 1_000000), + types.ErrUndercollaterized, }, { "max supply utilization", borrower, - coin.New(umeeDenom, 10_000000), + coin.New(umeeDenom, 9_000000), types.ErrMaxSupplyUtilization, }, { "atom borrow", @@ -1420,6 +1469,13 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow() { // collateral value is $50 (current) or $100 (historic) // collateral weights are always 0.25 in testing + // create an UMEE borrower using STABLE collateral + stableUmeeBorrower := s.newAccount(coin.New(stableDenom, 100_000000)) + s.supply(stableUmeeBorrower, coin.New(stableDenom, 100_000000)) + s.collateralize(stableUmeeBorrower, coin.New("u/"+stableDenom, 100_000000)) + s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) + // UMEE and STABLE have the same price but different collateral weights + tcs := []struct { msg string addr sdk.AccAddress @@ -1461,6 +1517,11 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow() { pumpborrower, coin.New(pumpDenom, 6_250000), nil, + }, { + "stable umee borrower", + stableUmeeBorrower, + coin.New(umeeDenom, 20_000000), + nil, }, } @@ -2165,11 +2226,11 @@ func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { coin.New("u/"+atomDenom, 3_527933), nil, }, { - "close factor < 1", + "close factor < 1 with auto-selected repay and reward denoms", liquidator, closeBorrower, - umeeDenom, - umeeDenom, + "", + "", coin.New(umeeDenom, 8_150541), coin.New("u/"+umeeDenom, 8_965596), nil, @@ -2187,7 +2248,22 @@ func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { _, err := srv.LeveragedLiquidate(ctx, msg) require.ErrorIs(err, tc.err, tc.msg) } else { - baseRewardDenom := types.ToTokenDenom(tc.expectedReward.Denom) + // borrower initial state + biBalance := app.BankKeeper.GetAllBalances(ctx, tc.borrower) + biCollateral := app.LeverageKeeper.GetBorrowerCollateral(ctx, tc.borrower) + biBorrowed := app.LeverageKeeper.GetBorrowerBorrows(ctx, tc.borrower) + + // adjust test case in empty-input scenarios, while preserving the msg + if msg.RepayDenom == "" { + if !biBorrowed.IsZero() { + tc.repayDenom = biBorrowed[0].Denom + } + } + if msg.RewardDenom == "" { + if !biCollateral.IsZero() { + tc.rewardDenom = types.ToTokenDenom(biCollateral[0].Denom) + } + } // initial state (borrowed denom) biUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) @@ -2195,12 +2271,7 @@ func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { // initial state (liquidated denom) liUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) - liExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, baseRewardDenom) - - // borrower initial state - biBalance := app.BankKeeper.GetAllBalances(ctx, tc.borrower) - biCollateral := app.LeverageKeeper.GetBorrowerCollateral(ctx, tc.borrower) - biBorrowed := app.LeverageKeeper.GetBorrowerBorrows(ctx, tc.borrower) + liExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, tc.rewardDenom) // liquidator initial state liBalance := app.BankKeeper.GetAllBalances(ctx, tc.liquidator) @@ -2215,7 +2286,7 @@ func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { // final state (liquidated denom) lfUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) - lfExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, baseRewardDenom) + lfExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, tc.rewardDenom) // borrower final state bfBalance := app.BankKeeper.GetAllBalances(ctx, tc.borrower) diff --git a/x/leverage/keeper/oracle.go b/x/leverage/keeper/oracle.go index 3ce0aaf0a1..7b27072b2e 100644 --- a/x/leverage/keeper/oracle.go +++ b/x/leverage/keeper/oracle.go @@ -119,6 +119,29 @@ func (k Keeper) TotalTokenValue(ctx sdk.Context, coins sdk.Coins, mode types.Pri return total, nil } +// ValueWithBorrowFactor returns the total value of all input tokens, each multiplied +// by borrow factor (which is the minimum of 2.0 and 1/collateral weight). It +// ignores unregistered and blacklisted tokens instead of returning an error, but +// will error on unavailable prices. +func (k Keeper) ValueWithBorrowFactor(ctx sdk.Context, coins sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { + total := sdk.ZeroDec() + + for _, c := range coins { + token, err := k.GetTokenSettings(ctx, c.Denom) + if err != nil { + continue + } + v, err := k.TokenValue(ctx, c, mode) + if err != nil { + return sdk.ZeroDec(), err + } + + total = total.Add(v.Mul(token.BorrowFactor())) + } + + return total, nil +} + // VisibleTokenValue functions like TotalTokenValue, but interprets missing oracle prices // as zero value instead of returning an error. func (k Keeper) VisibleTokenValue(ctx sdk.Context, coins sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { @@ -139,6 +162,21 @@ func (k Keeper) VisibleTokenValue(ctx sdk.Context, coins sdk.Coins, mode types.P return total, nil } +// VisibleUTokensValue converts uTokens to tokens and calls VisibleTokenValue. Errors on non-uTokens. +func (k Keeper) VisibleUTokensValue(ctx sdk.Context, uTokens sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { + tokens := sdk.NewCoins() + + for _, u := range uTokens { + t, err := k.ExchangeUToken(ctx, u) + if err != nil { + return sdk.ZeroDec(), err + } + tokens = tokens.Add(t) + } + + return k.VisibleTokenValue(ctx, tokens, mode) +} + // TokenWithValue creates a token of a given denom with an given USD value. // Returns an error on invalid price or denom. Rounds down, i.e. the // value of the token returned may be slightly less than the requested value. diff --git a/x/leverage/keeper/oracle_test.go b/x/leverage/keeper/oracle_test.go index d594617c25..14f0281552 100644 --- a/x/leverage/keeper/oracle_test.go +++ b/x/leverage/keeper/oracle_test.go @@ -62,18 +62,20 @@ func (m *mockOracleKeeper) Clear(denom string) { // Reset restores the mock oracle's prices to its default values. func (m *mockOracleKeeper) Reset() { m.symbolExchangeRates = map[string]sdk.Dec{ - "UMEE": sdk.MustNewDecFromStr("4.21"), - "ATOM": sdk.MustNewDecFromStr("39.38"), - "DAI": sdk.MustNewDecFromStr("1.00"), - "DUMP": sdk.MustNewDecFromStr("0.50"), // A token which has recently halved in price - "PUMP": sdk.MustNewDecFromStr("2.00"), // A token which has recently doubled in price + "UMEE": sdk.MustNewDecFromStr("4.21"), + "ATOM": sdk.MustNewDecFromStr("39.38"), + "DAI": sdk.MustNewDecFromStr("1.00"), + "DUMP": sdk.MustNewDecFromStr("0.50"), // A token which has recently halved in price + "PUMP": sdk.MustNewDecFromStr("2.00"), // A token which has recently doubled in price + "STABLE": sdk.MustNewDecFromStr("4.21"), // Same price as umee } m.historicExchangeRates = map[string]sdk.Dec{ - "UMEE": sdk.MustNewDecFromStr("4.21"), - "ATOM": sdk.MustNewDecFromStr("39.38"), - "DAI": sdk.MustNewDecFromStr("1.00"), - "DUMP": sdk.MustNewDecFromStr("1.00"), - "PUMP": sdk.MustNewDecFromStr("1.00"), + "UMEE": sdk.MustNewDecFromStr("4.21"), + "ATOM": sdk.MustNewDecFromStr("39.38"), + "DAI": sdk.MustNewDecFromStr("1.00"), + "DUMP": sdk.MustNewDecFromStr("1.00"), + "PUMP": sdk.MustNewDecFromStr("1.00"), + "STABLE": sdk.MustNewDecFromStr("4.21"), } } diff --git a/x/leverage/keeper/suite_test.go b/x/leverage/keeper/suite_test.go index 48426aa23c..d35bcba1f8 100644 --- a/x/leverage/keeper/suite_test.go +++ b/x/leverage/keeper/suite_test.go @@ -22,11 +22,12 @@ import ( ) const ( - umeeDenom = appparams.BondDenom - atomDenom = fixtures.AtomDenom - daiDenom = fixtures.DaiDenom - pumpDenom = "upump" - dumpDenom = "udump" + umeeDenom = appparams.BondDenom + atomDenom = fixtures.AtomDenom + daiDenom = fixtures.DaiDenom + pumpDenom = "upump" + dumpDenom = "udump" + stableDenom = "stable" ) type IntegrationTestSuite struct { @@ -82,6 +83,11 @@ func (s *IntegrationTestSuite) SetupTest() { // additional tokens for historacle testing require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(dumpDenom, "DUMP", 6))) require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(pumpDenom, "PUMP", 6))) + // additional tokens for borrow factor testing + stable := newToken(stableDenom, "STABLE", 6) + stable.CollateralWeight = sdk.MustNewDecFromStr("0.8") + stable.LiquidationThreshold = sdk.MustNewDecFromStr("0.9") + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, stable)) // override DefaultGenesis params with fixtures.Params app.LeverageKeeper.SetParams(ctx, fixtures.Params()) diff --git a/x/leverage/types/token.go b/x/leverage/types/token.go index ae27e696d5..8020ab7f61 100644 --- a/x/leverage/types/token.go +++ b/x/leverage/types/token.go @@ -15,6 +15,8 @@ const ( UTokenPrefix = "u/" ) +var halfDec = sdk.MustNewDecFromStr("0.5") + // HasUTokenPrefix detects the uToken prefix on a denom. func HasUTokenPrefix(denom string) bool { return strings.HasPrefix(denom, UTokenPrefix) @@ -167,6 +169,14 @@ func (t Token) AssertNotBlacklisted() error { return nil } +// BorrowFactor returns the minimum of 2.0 or 1 / collateralWeight. +func (t Token) BorrowFactor() sdk.Dec { + if t.CollateralWeight.LTE(halfDec) { + return sdk.MustNewDecFromStr("2.0") + } + return sdk.OneDec().Quo(t.CollateralWeight) +} + func defaultUmeeToken() Token { return Token{ BaseDenom: appparams.BondDenom, diff --git a/x/leverage/types/tx.pb.go b/x/leverage/types/tx.pb.go index 962699b6f2..2cb9394199 100644 --- a/x/leverage/types/tx.pb.go +++ b/x/leverage/types/tx.pb.go @@ -1303,7 +1303,9 @@ type MsgClient interface { // this initial borrow to exceed the liquidator's borrow limit as long as it is healthy by the end // of the transaction. Repay amount is calculated automatically, so the liquidator only specifies // repay and reward token denoms. For safety, the liquidator cannot exceed 80% of their borrow limit when - // executing this transaction, instead of the regular 100%. + // executing this transaction, instead of the regular 100%. Also allows repayment and reward denoms to + // be left blank - if not specified, the module will automatically select the first (alphabetically by denom) + // borrow and/or collateral on the target account and the proceed normally. LeveragedLiquidate(ctx context.Context, in *MsgLeveragedLiquidate, opts ...grpc.CallOption) (*MsgLeveragedLiquidateResponse, error) // SupplyCollateral combines the Supply and Collateralize actions. SupplyCollateral(ctx context.Context, in *MsgSupplyCollateral, opts ...grpc.CallOption) (*MsgSupplyCollateralResponse, error) @@ -1462,7 +1464,9 @@ type MsgServer interface { // this initial borrow to exceed the liquidator's borrow limit as long as it is healthy by the end // of the transaction. Repay amount is calculated automatically, so the liquidator only specifies // repay and reward token denoms. For safety, the liquidator cannot exceed 80% of their borrow limit when - // executing this transaction, instead of the regular 100%. + // executing this transaction, instead of the regular 100%. Also allows repayment and reward denoms to + // be left blank - if not specified, the module will automatically select the first (alphabetically by denom) + // borrow and/or collateral on the target account and the proceed normally. LeveragedLiquidate(context.Context, *MsgLeveragedLiquidate) (*MsgLeveragedLiquidateResponse, error) // SupplyCollateral combines the Supply and Collateralize actions. SupplyCollateral(context.Context, *MsgSupplyCollateral) (*MsgSupplyCollateralResponse, error)