From 95174a23671ed16f7497ef6b0edaa63a54f1343d Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 24 Jun 2024 15:19:20 -0600 Subject: [PATCH 1/6] feat(builders): non-ambient `strictPriceFeedProposalBuilder` in `priceFeedSupport.js` --- .../builders/scripts/vats/priceFeedSupport.js | 66 +++++++++++++------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/packages/builders/scripts/vats/priceFeedSupport.js b/packages/builders/scripts/vats/priceFeedSupport.js index 309d4f0a821..5f916d3c793 100644 --- a/packages/builders/scripts/vats/priceFeedSupport.js +++ b/packages/builders/scripts/vats/priceFeedSupport.js @@ -2,41 +2,33 @@ import { DEFAULT_CONTRACT_TERMS } from '../inter-protocol/price-feed-core.js'; -const ORACLE_ADDRESSES = [ - // XXX These are the oracle addresses. They must be provided before the chain - // is running, which means they must be known ahead of time. - // see https://github.com/Agoric/agoric-3-proposals/issues/5 - 'agoric1lu9hh5vgx05hmlpfu47hukershgdxctk6l5s05', - 'agoric15lpnq2mjsdhtztf6khp7mrsq66hyrssspy92pd', - 'agoric1mwm224epc4l3pjcz7qsxnudcuktpynwkmnfqfp', -]; +const { Fail } = assert; /** * modified copy of ../inter-protocol/price-feed-core.js * * @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const priceFeedProposalBuilder = async ( +export const strictPriceFeedProposalBuilder = async ( { publishRef, install }, - options = {}, + options, ) => { const { AGORIC_INSTANCE_NAME, IN_BRAND_LOOKUP, IN_BRAND_NAME = IN_BRAND_LOOKUP[IN_BRAND_LOOKUP.length - 1], + ORACLE_ADDRESSES, } = options; - const { GOV1ADDR, GOV2ADDR, GOV3ADDR } = process.env; - const oracleAddresses = - GOV1ADDR || GOV2ADDR || GOV3ADDR - ? [GOV1ADDR, GOV2ADDR, GOV3ADDR].filter(x => x) - : ORACLE_ADDRESSES; - assert(Array.isArray(oracleAddresses), 'oracleAddresses array is required'); + const oracleAddresses = ORACLE_ADDRESSES; + Array.isArray(oracleAddresses) || + Fail`ORACLE_ADDRESSES array is required; got ${oracleAddresses}`; - assert(AGORIC_INSTANCE_NAME, 'AGORIC_INSTANCE_NAME is required'); + AGORIC_INSTANCE_NAME || + Fail`AGORIC_INSTANCE_NAME is required; got ${AGORIC_INSTANCE_NAME}`; - assert.equal(IN_BRAND_LOOKUP[0], 'agoricNames'); - assert(IN_BRAND_NAME, 'brandIn is required'); + Array.isArray(IN_BRAND_LOOKUP) || + Fail`IN_BRAND_NAME array is required; got ${IN_BRAND_LOOKUP}`; return harden({ sourceSpec: '@agoric/inter-protocol/src/proposals/price-feed-proposal.js', @@ -60,3 +52,39 @@ export const priceFeedProposalBuilder = async ( ], }); }; + +/** + * @deprecated use `strictPriceFeedProposalBuilder` and specify arguments instead + * @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} + */ +export const deprecatedPriceFeedProposalBuilder = async (powers, options) => { + console.warn( + 'deprecated ambient `priceFeedProposalBuilder`; use `strictPriceFeedProposalBuilder` instead', + ); + + const DEFAULT_ORACLE_ADDRESSES = [ + // XXX These are the oracle addresses. They must be provided before the chain + // is running, which means they must be known ahead of time. + // see https://github.com/Agoric/agoric-3-proposals/issues/5 + 'agoric1lu9hh5vgx05hmlpfu47hukershgdxctk6l5s05', + 'agoric15lpnq2mjsdhtztf6khp7mrsq66hyrssspy92pd', + 'agoric1mwm224epc4l3pjcz7qsxnudcuktpynwkmnfqfp', + ]; + + const { GOV1ADDR, GOV2ADDR, GOV3ADDR } = process.env; + const governanceAddressEnv = [GOV1ADDR, GOV2ADDR, GOV3ADDR].filter(x => x); + const ORACLE_ADDRESSES = governanceAddressEnv.length + ? governanceAddressEnv + : DEFAULT_ORACLE_ADDRESSES; + + return strictPriceFeedProposalBuilder(powers, { + ...options, + ORACLE_ADDRESSES, + }); +}; + +/** + * @deprecated use `strictPriceFeedProposalBuilder` and specify arguments instead + * @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} + */ +export const priceFeedProposalBuilder = deprecatedPriceFeedProposalBuilder; From 5cc190d96c93e2d8d97454ae7df85b7ca9697003 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 24 Jun 2024 15:20:25 -0600 Subject: [PATCH 2/6] feat(app): establish mechanism for adding core proposals by `upgradePlan.name` --- .../proposals/a:upgrade-next/package.json | 2 +- golang/cosmos/app/app.go | 152 ++++++++++++++++-- golang/cosmos/vm/core_proposals.go | 24 ++- 3 files changed, 161 insertions(+), 17 deletions(-) diff --git a/a3p-integration/proposals/a:upgrade-next/package.json b/a3p-integration/proposals/a:upgrade-next/package.json index 8a30802df8a..06868e9bea8 100644 --- a/a3p-integration/proposals/a:upgrade-next/package.json +++ b/a3p-integration/proposals/a:upgrade-next/package.json @@ -2,7 +2,7 @@ "agoricProposal": { "releaseNotes": false, "sdkImageTag": "unreleased", - "planName": "UNRELEASED_UPGRADE", + "planName": "UNRELEASED_A3P_INTEGRATION", "upgradeInfo": { "coreProposals": [] }, diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go index 997634dd926..8cc7d52d0e9 100644 --- a/golang/cosmos/app/app.go +++ b/golang/cosmos/app/app.go @@ -10,6 +10,8 @@ import ( "os" "path/filepath" "runtime/debug" + "strings" + "text/template" "time" sdkioerrors "cosmossdk.io/errors" @@ -110,6 +112,8 @@ import ( appante "github.com/Agoric/agoric-sdk/golang/cosmos/ante" agorictypes "github.com/Agoric/agoric-sdk/golang/cosmos/types" + + // conv "github.com/Agoric/agoric-sdk/golang/cosmos/types/conv" "github.com/Agoric/agoric-sdk/golang/cosmos/vm" "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset" swingsetclient "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/client" @@ -908,8 +912,10 @@ func NewAgoricApp( } var upgradeNamesOfThisVersion = map[string]bool{ - "UNRELEASED_UPGRADE": true, - "UNRELEASED_TEST_UPGRADE": true, + "UNRELEASED_BASIC": true, // no-frills + "UNRELEASED_A3P_INTEGRATION": true, + "UNRELEASED_main": true, + "UNRELEASED_devnet": true, } func isFirstTimeUpgradeOfThisVersion(app *GaiaApp, ctx sdk.Context) bool { @@ -921,6 +927,131 @@ func isFirstTimeUpgradeOfThisVersion(app *GaiaApp, ctx sdk.Context) bool { return true } +type BrandInfo struct { + Name string + Oracle string +} + +// upgradePriceFeedCoreProposalSteps returns the core proposal steps for the +// price feed upgrade and associated changes to scaledPriceAuthority and +// vaultManager. +func upgradePriceFeedCoreProposalSteps(upgradeName string) ([]vm.CoreProposalStep, error) { + + t := template.Must(template.New("").Parse(`{ + "module": "@agoric/builders/scripts/vats/priceFeedSupport.js", + "entrypoint": {{.entrypointJson}}, + "args": [{ + "AGORIC_INSTANCE_NAME": {{.instanceNameJson}}, + "ORACLE_ADDRESSES": {{.oracleAddressesJson}}, + "IN_BRAND_LOOKUP": {{.inBrandLookupJson}}, + "IN_BRAND_DECIMALS": 6, + "OUT_BRAND_LOOKUP": ["agoricNames", "oracleBrand", "USD"], + "OUT_BRAND_DECIMALS": 4 + }] + }`)) + + var oracleAddresses []string + + var entrypoint string + switch upgradeName { + case "UNRELEASED_A3P_INTEGRATION": + entrypoint = "deprecatedPriceFeedProposalBuilder" + case "UNRELEASED_main": + oracleAddresses = []string{ + "agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78", // DSRV + "agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p01", // Stakin + "agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8", // node + "agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr", // Simply Staking + "agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj", // P2P + } + entrypoint = "strictPriceFeedProposalBuilder" + case "UNRELEASED_devnet": + oracleAddresses = []string{ + "agoric1lw4e4aas9q84tq0q92j85rwjjjapf8dmnllnft", // DSRV + "agoric1qj07c7vfk3knqdral0sej7fa6eavkdn8vd8etf01", // Simply Staking + "agoric1ra0g6crtsy6r3qnpu7ruvm7qd4wjnznyzg5nu4", // node + "agoric1zj6vrrrjq4gsyr9lw7dplv4vyejg3p8j2urm82", // Stakin + "agoric10vjkvkmpp9e356xeh6qqlhrny2htyzp8hf88fk", // P2P + } + entrypoint = "strictPriceFeedProposalBuilder" + + // No price feed upgrade for this version. + case "UNRELEASED_BASIC": + } + + if entrypoint == "" { + return []vm.CoreProposalStep{}, nil + } + + entrypointJson, err := json.Marshal(entrypoint) + if err != nil { + return nil, err + } + + var inBrands []BrandInfo + switch upgradeName { + case "UNRELEASED_A3P_INTEGRATION", "UNRELEASED_main": + inBrands = []BrandInfo{ + {"ATOM", "ATOM"}, + {"stATOM", "stAtom"}, + {"stOSMO", "stOSMO"}, + {"stTIA", "stTIA"}, + {"stkATOM", "stkAtom"}, + } + case "UNRELEASED_devnet": + inBrands = []BrandInfo{ + {"ATOM", "ATOM"}, + {"stTIA", "stTIA"}, + {"stkATOM", "stkAtom"}, + } + } + + oracleAddressesJson, err := json.Marshal(oracleAddresses) + if err != nil { + return nil, err + } + + proposals := make(vm.CoreProposalStep, 0, len(inBrands)) + for _, inBrand := range inBrands { + instanceName := inBrand.Name + "-USD price feed" + instanceNameJson, err := json.Marshal(instanceName) + if err != nil { + return nil, err + } + inBrandLookup := []string{"agoricNames", "oracleBrand", inBrand.Oracle} + inBrandLookupJson, err := json.Marshal(inBrandLookup) + if err != nil { + return nil, err + } + + var result strings.Builder + err = t.Execute(&result, map[string]any{ + "entrypointJson": string(entrypointJson), + "inBrandLookupJson": string(inBrandLookupJson), + "instanceNameJson": string(instanceNameJson), + "oracleAddressesJson": string(oracleAddressesJson), + }) + if err != nil { + return nil, err + } + + jsonStr := result.String() + jsonBz := []byte(jsonStr) + if !json.Valid(jsonBz) { + return nil, fmt.Errorf("invalid JSON: %s", jsonStr) + } + proposals = append(proposals, vm.ArbitraryCoreProposal{Json: jsonBz}) + } + return []vm.CoreProposalStep{ + // Add new vats for price feeds. The existing ones will be retired shortly. + vm.CoreProposalStepForModules(proposals...), + // Add new auction contract. The old one will be retired shortly. + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/add-auction.js"), + // upgrade vaultFactory. + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/upgradeVaults.js"), + }, nil +} + // unreleasedUpgradeHandler performs standard upgrade actions plus custom actions for the unreleased upgrade. func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgradetypes.Plan, module.VersionMap) (module.VersionMap, error) { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVm module.VersionMap) (module.VersionMap, error) { @@ -947,19 +1078,12 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte "@agoric/builders/scripts/vats/init-localchain.js", "@agoric/builders/scripts/vats/init-transfer.js", ), - // Add new vats for price feeds. The existing ones will be retired shortly. - vm.CoreProposalStepForModules( - "@agoric/builders/scripts/vats/updateAtomPriceFeed.js", - "@agoric/builders/scripts/vats/updateStAtomPriceFeed.js", - "@agoric/builders/scripts/vats/updateStOsmoPriceFeed.js", - "@agoric/builders/scripts/vats/updateStTiaPriceFeed.js", - "@agoric/builders/scripts/vats/updateStkAtomPriceFeed.js", - ), - // Add new auction contract. The old one will be retired shortly. - vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/add-auction.js"), - // upgrade vaultFactory. - vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/upgradeVaults.js"), } + priceFeedSteps, err := upgradePriceFeedCoreProposalSteps(targetUpgrade) + if err != nil { + return nil, err + } + CoreProposalSteps = append(CoreProposalSteps, priceFeedSteps...) } app.upgradeDetails = &upgradeDetails{ diff --git a/golang/cosmos/vm/core_proposals.go b/golang/cosmos/vm/core_proposals.go index 44aa9bd83f9..65d459f22d4 100644 --- a/golang/cosmos/vm/core_proposals.go +++ b/golang/cosmos/vm/core_proposals.go @@ -1,5 +1,10 @@ package vm +import ( + "encoding/json" + "fmt" +) + // CoreProposalStep is a set of core proposal configs which are executed // concurrently type CoreProposalStep []Jsonable @@ -11,12 +16,27 @@ type CoreProposals struct { Steps []CoreProposalStep `json:"steps"` } +type ArbitraryCoreProposal struct { + Json json.RawMessage +} + +func NewArbitraryCoreProposal(jsonStr string) *ArbitraryCoreProposal { + return &ArbitraryCoreProposal{Json: []byte(jsonStr)} +} + // CoreProposalStepForModules generates a single core proposal step from // the given modules, which will be executed concurrently during that step -func CoreProposalStepForModules(modules ...string) CoreProposalStep { +func CoreProposalStepForModules(modules ...Jsonable) CoreProposalStep { step := make([]Jsonable, len(modules)) for i := range modules { - step[i] = modules[i] + switch m := modules[i].(type) { + case ArbitraryCoreProposal: + step[i] = m.Json + case string: + step[i] = m + default: + panic(fmt.Errorf("unexpected step type %T", m)) + } } return step } From 52f02b75b6706ee455a32ff83617dd5afb7342a7 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 24 Jun 2024 21:20:03 -0600 Subject: [PATCH 3/6] fix(builders): use proper `oracleBrand` subkey case --- golang/cosmos/app/app.go | 35 ++++++++----------- .../scripts/vats/updateStAtomPriceFeed.js | 2 +- .../scripts/vats/updateStkAtomPriceFeed.js | 2 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go index 8cc7d52d0e9..ba400e763e8 100644 --- a/golang/cosmos/app/app.go +++ b/golang/cosmos/app/app.go @@ -927,11 +927,6 @@ func isFirstTimeUpgradeOfThisVersion(app *GaiaApp, ctx sdk.Context) bool { return true } -type BrandInfo struct { - Name string - Oracle string -} - // upgradePriceFeedCoreProposalSteps returns the core proposal steps for the // price feed upgrade and associated changes to scaledPriceAuthority and // vaultManager. @@ -988,21 +983,21 @@ func upgradePriceFeedCoreProposalSteps(upgradeName string) ([]vm.CoreProposalSte return nil, err } - var inBrands []BrandInfo + var inBrandNames []string switch upgradeName { case "UNRELEASED_A3P_INTEGRATION", "UNRELEASED_main": - inBrands = []BrandInfo{ - {"ATOM", "ATOM"}, - {"stATOM", "stAtom"}, - {"stOSMO", "stOSMO"}, - {"stTIA", "stTIA"}, - {"stkATOM", "stkAtom"}, + inBrandNames = []string{ + "ATOM", + "stATOM", + "stOSMO", + "stTIA", + "stkATOM", } case "UNRELEASED_devnet": - inBrands = []BrandInfo{ - {"ATOM", "ATOM"}, - {"stTIA", "stTIA"}, - {"stkATOM", "stkAtom"}, + inBrandNames = []string{ + "ATOM", + "stTIA", + "stkATOM", } } @@ -1011,14 +1006,14 @@ func upgradePriceFeedCoreProposalSteps(upgradeName string) ([]vm.CoreProposalSte return nil, err } - proposals := make(vm.CoreProposalStep, 0, len(inBrands)) - for _, inBrand := range inBrands { - instanceName := inBrand.Name + "-USD price feed" + proposals := make(vm.CoreProposalStep, 0, len(inBrandNames)) + for _, inBrandName := range inBrandNames { + instanceName := inBrandName + "-USD price feed" instanceNameJson, err := json.Marshal(instanceName) if err != nil { return nil, err } - inBrandLookup := []string{"agoricNames", "oracleBrand", inBrand.Oracle} + inBrandLookup := []string{"agoricNames", "oracleBrand", inBrandName} inBrandLookupJson, err := json.Marshal(inBrandLookup) if err != nil { return nil, err diff --git a/packages/builders/scripts/vats/updateStAtomPriceFeed.js b/packages/builders/scripts/vats/updateStAtomPriceFeed.js index 6d3c226b30e..456c1b73fff 100644 --- a/packages/builders/scripts/vats/updateStAtomPriceFeed.js +++ b/packages/builders/scripts/vats/updateStAtomPriceFeed.js @@ -3,7 +3,7 @@ import { priceFeedProposalBuilder } from './priceFeedSupport.js'; const OPTIONS = { AGORIC_INSTANCE_NAME: 'stATOM-USD price feed', - IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'stAtom'], + IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'stATOM'], OUT_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'USD'], }; diff --git a/packages/builders/scripts/vats/updateStkAtomPriceFeed.js b/packages/builders/scripts/vats/updateStkAtomPriceFeed.js index 2fd2a671f1e..1226b7e201f 100644 --- a/packages/builders/scripts/vats/updateStkAtomPriceFeed.js +++ b/packages/builders/scripts/vats/updateStkAtomPriceFeed.js @@ -3,7 +3,7 @@ import { priceFeedProposalBuilder } from './priceFeedSupport.js'; const OPTIONS = { AGORIC_INSTANCE_NAME: 'stkATOM-USD price feed', - IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'stkAtom'], + IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'stkATOM'], OUT_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'USD'], }; From ddc072d7f0619c6401f000b7e24db38e9a259467 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Mon, 24 Jun 2024 22:30:22 -0600 Subject: [PATCH 4/6] chore(cosmos): extract `app/upgrade.go` --- golang/cosmos/app/app.go | 197 -------------------------------- golang/cosmos/app/upgrade.go | 215 +++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 197 deletions(-) create mode 100644 golang/cosmos/app/upgrade.go diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go index ba400e763e8..bb5dfc4e8bf 100644 --- a/golang/cosmos/app/app.go +++ b/golang/cosmos/app/app.go @@ -10,8 +10,6 @@ import ( "os" "path/filepath" "runtime/debug" - "strings" - "text/template" "time" sdkioerrors "cosmossdk.io/errors" @@ -911,201 +909,6 @@ func NewAgoricApp( return app } -var upgradeNamesOfThisVersion = map[string]bool{ - "UNRELEASED_BASIC": true, // no-frills - "UNRELEASED_A3P_INTEGRATION": true, - "UNRELEASED_main": true, - "UNRELEASED_devnet": true, -} - -func isFirstTimeUpgradeOfThisVersion(app *GaiaApp, ctx sdk.Context) bool { - for name := range upgradeNamesOfThisVersion { - if app.UpgradeKeeper.GetDoneHeight(ctx, name) != 0 { - return false - } - } - return true -} - -// upgradePriceFeedCoreProposalSteps returns the core proposal steps for the -// price feed upgrade and associated changes to scaledPriceAuthority and -// vaultManager. -func upgradePriceFeedCoreProposalSteps(upgradeName string) ([]vm.CoreProposalStep, error) { - - t := template.Must(template.New("").Parse(`{ - "module": "@agoric/builders/scripts/vats/priceFeedSupport.js", - "entrypoint": {{.entrypointJson}}, - "args": [{ - "AGORIC_INSTANCE_NAME": {{.instanceNameJson}}, - "ORACLE_ADDRESSES": {{.oracleAddressesJson}}, - "IN_BRAND_LOOKUP": {{.inBrandLookupJson}}, - "IN_BRAND_DECIMALS": 6, - "OUT_BRAND_LOOKUP": ["agoricNames", "oracleBrand", "USD"], - "OUT_BRAND_DECIMALS": 4 - }] - }`)) - - var oracleAddresses []string - - var entrypoint string - switch upgradeName { - case "UNRELEASED_A3P_INTEGRATION": - entrypoint = "deprecatedPriceFeedProposalBuilder" - case "UNRELEASED_main": - oracleAddresses = []string{ - "agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78", // DSRV - "agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p01", // Stakin - "agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8", // node - "agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr", // Simply Staking - "agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj", // P2P - } - entrypoint = "strictPriceFeedProposalBuilder" - case "UNRELEASED_devnet": - oracleAddresses = []string{ - "agoric1lw4e4aas9q84tq0q92j85rwjjjapf8dmnllnft", // DSRV - "agoric1qj07c7vfk3knqdral0sej7fa6eavkdn8vd8etf01", // Simply Staking - "agoric1ra0g6crtsy6r3qnpu7ruvm7qd4wjnznyzg5nu4", // node - "agoric1zj6vrrrjq4gsyr9lw7dplv4vyejg3p8j2urm82", // Stakin - "agoric10vjkvkmpp9e356xeh6qqlhrny2htyzp8hf88fk", // P2P - } - entrypoint = "strictPriceFeedProposalBuilder" - - // No price feed upgrade for this version. - case "UNRELEASED_BASIC": - } - - if entrypoint == "" { - return []vm.CoreProposalStep{}, nil - } - - entrypointJson, err := json.Marshal(entrypoint) - if err != nil { - return nil, err - } - - var inBrandNames []string - switch upgradeName { - case "UNRELEASED_A3P_INTEGRATION", "UNRELEASED_main": - inBrandNames = []string{ - "ATOM", - "stATOM", - "stOSMO", - "stTIA", - "stkATOM", - } - case "UNRELEASED_devnet": - inBrandNames = []string{ - "ATOM", - "stTIA", - "stkATOM", - } - } - - oracleAddressesJson, err := json.Marshal(oracleAddresses) - if err != nil { - return nil, err - } - - proposals := make(vm.CoreProposalStep, 0, len(inBrandNames)) - for _, inBrandName := range inBrandNames { - instanceName := inBrandName + "-USD price feed" - instanceNameJson, err := json.Marshal(instanceName) - if err != nil { - return nil, err - } - inBrandLookup := []string{"agoricNames", "oracleBrand", inBrandName} - inBrandLookupJson, err := json.Marshal(inBrandLookup) - if err != nil { - return nil, err - } - - var result strings.Builder - err = t.Execute(&result, map[string]any{ - "entrypointJson": string(entrypointJson), - "inBrandLookupJson": string(inBrandLookupJson), - "instanceNameJson": string(instanceNameJson), - "oracleAddressesJson": string(oracleAddressesJson), - }) - if err != nil { - return nil, err - } - - jsonStr := result.String() - jsonBz := []byte(jsonStr) - if !json.Valid(jsonBz) { - return nil, fmt.Errorf("invalid JSON: %s", jsonStr) - } - proposals = append(proposals, vm.ArbitraryCoreProposal{Json: jsonBz}) - } - return []vm.CoreProposalStep{ - // Add new vats for price feeds. The existing ones will be retired shortly. - vm.CoreProposalStepForModules(proposals...), - // Add new auction contract. The old one will be retired shortly. - vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/add-auction.js"), - // upgrade vaultFactory. - vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/upgradeVaults.js"), - }, nil -} - -// unreleasedUpgradeHandler performs standard upgrade actions plus custom actions for the unreleased upgrade. -func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgradetypes.Plan, module.VersionMap) (module.VersionMap, error) { - return func(ctx sdk.Context, plan upgradetypes.Plan, fromVm module.VersionMap) (module.VersionMap, error) { - app.CheckControllerInited(false) - - CoreProposalSteps := []vm.CoreProposalStep{} - - // These CoreProposalSteps are not idempotent and should only be executed - // as part of the first upgrade using this handler on any given chain. - if isFirstTimeUpgradeOfThisVersion(app, ctx) { - // Each CoreProposalStep runs sequentially, and can be constructed from - // one or more modules executing in parallel within the step. - CoreProposalSteps = []vm.CoreProposalStep{ - // Upgrade Zoe + ZCF - vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replace-zoe.js"), - // Revive KREAd characters - vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/revive-kread.js"), - - // upgrade the provisioning vat - vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replace-provisioning.js"), - // Enable low-level Orchestration. - vm.CoreProposalStepForModules( - "@agoric/builders/scripts/vats/init-network.js", - "@agoric/builders/scripts/vats/init-localchain.js", - "@agoric/builders/scripts/vats/init-transfer.js", - ), - } - priceFeedSteps, err := upgradePriceFeedCoreProposalSteps(targetUpgrade) - if err != nil { - return nil, err - } - CoreProposalSteps = append(CoreProposalSteps, priceFeedSteps...) - } - - app.upgradeDetails = &upgradeDetails{ - // Record the plan to send to SwingSet - Plan: plan, - // Core proposals that should run during the upgrade block - // These will be merged with any coreProposals specified in the - // upgradeInfo field of the upgrade plan ran as subsequent steps - CoreProposals: vm.CoreProposalsFromSteps(CoreProposalSteps...), - } - - // Always run module migrations - mvm, err := app.mm.RunMigrations(ctx, app.configurator, fromVm) - if err != nil { - return mvm, err - } - - m := swingsetkeeper.NewMigrator(app.SwingSetKeeper) - err = m.MigrateParams(ctx) - if err != nil { - return mvm, err - } - - return mvm, nil - } -} - // normalizeModuleAccount ensures that the given account is a module account, // initializing or updating it if necessary. The account name must be listed in maccPerms. func normalizeModuleAccount(ctx sdk.Context, ak authkeeper.AccountKeeper, name string) { diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go new file mode 100644 index 00000000000..519083ddf4c --- /dev/null +++ b/golang/cosmos/app/upgrade.go @@ -0,0 +1,215 @@ +package gaia + +import ( + "encoding/json" + "fmt" + "strings" + "text/template" + + "github.com/Agoric/agoric-sdk/golang/cosmos/vm" + swingsetkeeper "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/keeper" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" +) + +var upgradeNamesOfThisVersion = map[string]bool{ + "UNRELEASED_BASIC": true, // no-frills + "UNRELEASED_A3P_INTEGRATION": true, + "UNRELEASED_main": true, + "UNRELEASED_devnet": true, +} + +func isFirstTimeUpgradeOfThisVersion(app *GaiaApp, ctx sdk.Context) bool { + for name := range upgradeNamesOfThisVersion { + if app.UpgradeKeeper.GetDoneHeight(ctx, name) != 0 { + return false + } + } + return true +} + +// upgradePriceFeedCoreProposalSteps returns the core proposal steps for the +// price feed upgrade and associated changes to scaledPriceAuthority and +// vaultManager. +func upgradePriceFeedCoreProposalSteps(upgradeName string) ([]vm.CoreProposalStep, error) { + isThisUpgrade := func(expectedUpgradeName string) bool { + if !upgradeNamesOfThisVersion[expectedUpgradeName] { + panic(fmt.Errorf("invalid upgrade name: %s", expectedUpgradeName)) + } + return upgradeName == expectedUpgradeName + } + + t := template.Must(template.New("").Parse(`{ + "module": "@agoric/builders/scripts/vats/priceFeedSupport.js", + "entrypoint": {{.entrypointJson}}, + "args": [{ + "AGORIC_INSTANCE_NAME": {{.instanceNameJson}}, + "ORACLE_ADDRESSES": {{.oracleAddressesJson}}, + "IN_BRAND_LOOKUP": {{.inBrandLookupJson}}, + "IN_BRAND_DECIMALS": 6, + "OUT_BRAND_LOOKUP": ["agoricNames", "oracleBrand", "USD"], + "OUT_BRAND_DECIMALS": 4 + }] + }`)) + + var oracleAddresses []string + + var entrypoint string + switch { + case isThisUpgrade("UNRELEASED_A3P_INTEGRATION"): + entrypoint = "deprecatedPriceFeedProposalBuilder" + case isThisUpgrade("UNRELEASED_main"): + oracleAddresses = []string{ + "agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78", // DSRV + "agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p", // Stakin + "agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8", // 01node + "agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr", // Simply Staking + "agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj", // P2P + } + entrypoint = "strictPriceFeedProposalBuilder" + case isThisUpgrade("UNRELEASED_devnet"): + oracleAddresses = []string{ + "agoric1lw4e4aas9q84tq0q92j85rwjjjapf8dmnllnft", // DSRV + "agoric1zj6vrrrjq4gsyr9lw7dplv4vyejg3p8j2urm82", // Stakin + "agoric1ra0g6crtsy6r3qnpu7ruvm7qd4wjnznyzg5nu4", // 01node + "agoric1qj07c7vfk3knqdral0sej7fa6eavkdn8vd8etf", // Simply Staking + "agoric10vjkvkmpp9e356xeh6qqlhrny2htyzp8hf88fk", // P2P + } + entrypoint = "strictPriceFeedProposalBuilder" + + // No price feed upgrade for this version. + case isThisUpgrade("UNRELEASED_BASIC"): + } + + if entrypoint == "" { + return []vm.CoreProposalStep{}, nil + } + + entrypointJson, err := json.Marshal(entrypoint) + if err != nil { + return nil, err + } + + var inBrandNames []string + switch { + case isThisUpgrade("UNRELEASED_A3P_INTEGRATION"), isThisUpgrade("UNRELEASED_main"): + inBrandNames = []string{ + "ATOM", + "stATOM", + "stOSMO", + "stTIA", + "stkATOM", + } + case isThisUpgrade("UNRELEASED_devnet"): + inBrandNames = []string{ + "ATOM", + "stTIA", + "stkATOM", + } + } + + oracleAddressesJson, err := json.Marshal(oracleAddresses) + if err != nil { + return nil, err + } + + proposals := make(vm.CoreProposalStep, 0, len(inBrandNames)) + for _, inBrandName := range inBrandNames { + instanceName := inBrandName + "-USD price feed" + instanceNameJson, err := json.Marshal(instanceName) + if err != nil { + return nil, err + } + inBrandLookup := []string{"agoricNames", "oracleBrand", inBrandName} + inBrandLookupJson, err := json.Marshal(inBrandLookup) + if err != nil { + return nil, err + } + + var result strings.Builder + err = t.Execute(&result, map[string]any{ + "entrypointJson": string(entrypointJson), + "inBrandLookupJson": string(inBrandLookupJson), + "instanceNameJson": string(instanceNameJson), + "oracleAddressesJson": string(oracleAddressesJson), + }) + if err != nil { + return nil, err + } + + jsonStr := result.String() + jsonBz := []byte(jsonStr) + if !json.Valid(jsonBz) { + return nil, fmt.Errorf("invalid JSON: %s", jsonStr) + } + proposals = append(proposals, vm.ArbitraryCoreProposal{Json: jsonBz}) + } + return []vm.CoreProposalStep{ + // Add new vats for price feeds. The existing ones will be retired shortly. + vm.CoreProposalStepForModules(proposals...), + // Add new auction contract. The old one will be retired shortly. + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/add-auction.js"), + // upgrade vaultFactory. + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/upgradeVaults.js"), + }, nil +} + +// unreleasedUpgradeHandler performs standard upgrade actions plus custom actions for the unreleased upgrade. +func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgradetypes.Plan, module.VersionMap) (module.VersionMap, error) { + return func(ctx sdk.Context, plan upgradetypes.Plan, fromVm module.VersionMap) (module.VersionMap, error) { + app.CheckControllerInited(false) + + CoreProposalSteps := []vm.CoreProposalStep{} + + // These CoreProposalSteps are not idempotent and should only be executed + // as part of the first upgrade using this handler on any given chain. + if isFirstTimeUpgradeOfThisVersion(app, ctx) { + // Each CoreProposalStep runs sequentially, and can be constructed from + // one or more modules executing in parallel within the step. + CoreProposalSteps = []vm.CoreProposalStep{ + // Upgrade Zoe + ZCF + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replace-zoe.js"), + // Revive KREAd characters + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/revive-kread.js"), + + // upgrade the provisioning vat + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replace-provisioning.js"), + // Enable low-level Orchestration. + vm.CoreProposalStepForModules( + "@agoric/builders/scripts/vats/init-network.js", + "@agoric/builders/scripts/vats/init-localchain.js", + "@agoric/builders/scripts/vats/init-transfer.js", + ), + } + priceFeedSteps, err := upgradePriceFeedCoreProposalSteps(targetUpgrade) + if err != nil { + return nil, err + } + CoreProposalSteps = append(CoreProposalSteps, priceFeedSteps...) + } + + app.upgradeDetails = &upgradeDetails{ + // Record the plan to send to SwingSet + Plan: plan, + // Core proposals that should run during the upgrade block + // These will be merged with any coreProposals specified in the + // upgradeInfo field of the upgrade plan ran as subsequent steps + CoreProposals: vm.CoreProposalsFromSteps(CoreProposalSteps...), + } + + // Always run module migrations + mvm, err := app.mm.RunMigrations(ctx, app.configurator, fromVm) + if err != nil { + return mvm, err + } + + m := swingsetkeeper.NewMigrator(app.SwingSetKeeper) + err = m.MigrateParams(ctx) + if err != nil { + return mvm, err + } + + return mvm, nil + } +} From b3182a49eb3ab40741b6136c28e74022e03c5c81 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Tue, 25 Jun 2024 20:01:51 +0000 Subject: [PATCH 5/6] chore: fix error handling of upgrade vaults proposal --- .../src/proposals/upgrade-vaults.js | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/inter-protocol/src/proposals/upgrade-vaults.js b/packages/inter-protocol/src/proposals/upgrade-vaults.js index 0c7ee64caaf..880d25be89f 100644 --- a/packages/inter-protocol/src/proposals/upgrade-vaults.js +++ b/packages/inter-protocol/src/proposals/upgrade-vaults.js @@ -6,19 +6,24 @@ import { makeTracer } from '@agoric/internal/src/index.js'; const trace = makeTracer('upgrade Vaults proposal'); // stand-in for Promise.any() which isn't available at this point. +/** @param {Promise[]} promises */ const any = promises => new Promise((resolve, reject) => { for (const promise of promises) { - promise.then(resolve); + void promise.then(resolve, () => {}); } void Promise.allSettled(promises).then(results => { - const rejects = results.filter(({ status }) => status === 'rejected'); + const rejects = /** @type {PromiseRejectedResult[]} */ ( + results.filter(({ status }) => status === 'rejected') + ); if (rejects.length === results.length) { - // @ts-expect-error TypeScript doesn't know enough - const messages = rejects.map(({ message }) => message); + const messages = rejects.map( + ({ reason: { message } }) => message || 'no error message', + ); const aggregate = new Error(messages.join(';')); - // @ts-expect-error TypeScript doesn't know enough - aggregate.errors = rejects.map(({ reason }) => reason); + /** @type {any} */ (aggregate).errors = rejects.map( + ({ reason }) => reason, + ); reject(aggregate); } }); @@ -139,7 +144,7 @@ export const upgradeVaults = async (powers, { options }) => { newPrivateArgs, ); - console.log('upgraded vaultFactory.', upgradeResult); + trace('upgraded vaultFactory.', upgradeResult); }; // Wait for at least one new price feed to be ready before upgrading Vaults @@ -165,6 +170,14 @@ export const upgradeVaults = async (powers, { options }) => { // @ts-expect-error auctioneerKit is non-null except between auctioneerKitProducer.reset() and auctioneerKitProducer.resolve() auctioneerKit.instance, ); + trace(`upgrading complete`, price); + }, + error => { + console.error( + 'Failed to upgrade vaultFactory', + error.message, + ...(error.errors || []), + ); }, ); From ea568a266541959f87476d0c4f16c0ee0cf9abdc Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Tue, 25 Jun 2024 22:23:17 +0000 Subject: [PATCH 6/6] fix: retry upgrade vaults price quote --- .../src/proposals/upgrade-vaults.js | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/inter-protocol/src/proposals/upgrade-vaults.js b/packages/inter-protocol/src/proposals/upgrade-vaults.js index 880d25be89f..7c8547bf2c6 100644 --- a/packages/inter-protocol/src/proposals/upgrade-vaults.js +++ b/packages/inter-protocol/src/proposals/upgrade-vaults.js @@ -2,6 +2,7 @@ import { E } from '@endo/far'; import { makeNotifierFromAsyncIterable } from '@agoric/notifier'; import { AmountMath } from '@agoric/ertp/src/index.js'; import { makeTracer } from '@agoric/internal/src/index.js'; +import { isUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js'; const trace = makeTracer('upgrade Vaults proposal'); @@ -150,9 +151,28 @@ export const upgradeVaults = async (powers, { options }) => { // Wait for at least one new price feed to be ready before upgrading Vaults void E.when( any( - Object.values(vaultBrands).map(brand => - E(priceAuthority).quoteGiven(AmountMath.make(brand, 10n), istBrand), - ), + Object.values(vaultBrands).map(async brand => { + const getQuote = async lastRejectionReason => { + await null; + try { + return await E(priceAuthority).quoteGiven( + AmountMath.make(brand, 10n), + istBrand, + ); + } catch (reason) { + if ( + isUpgradeDisconnection(reason) && + (!lastRejectionReason || + reason.incarnationNumber > + lastRejectionReason.incarnationNumber) + ) { + return getQuote(reason); + } + throw reason; + } + }; + return getQuote(null); + }), ), async price => { trace(`upgrading after delay`, price);