diff --git a/test/e2e/specs/liquidation.spec.js b/test/e2e/specs/liquidation.spec.js index 1ee9313d..99a50310 100644 --- a/test/e2e/specs/liquidation.spec.js +++ b/test/e2e/specs/liquidation.spec.js @@ -195,6 +195,12 @@ describe('Wallet App Test Cases', () => { }); }); + context('Old auctioneer', () => { + it('should pause the old auctioneer', () => { + cy.pauseOldAuctioneer(); + }); + }); + context('Adjusting auction params from econ-gov', () => { it('should allow gov1 to create a proposal', () => { cy.skipWhen(AGORIC_NET !== networks.LOCAL); @@ -304,6 +310,9 @@ describe('Wallet App Test Cases', () => { }); context('Creating vaults and changing ATOM price', () => { + it('should pause the old auctioneer', () => { + cy.pauseOldAuctioneer(); + }); it( 'should connect with the wallet', { @@ -374,6 +383,10 @@ describe('Wallet App Test Cases', () => { }); context('Place bids and make all vaults enter liquidation', () => { + it('should pause the old auctioneer', () => { + cy.pauseOldAuctioneer(); + }); + it('should create a vault minting 400 ISTs and giving 80 ATOMs as collateral', () => { cy.skipWhen(AGORIC_NET !== networks.LOCAL); cy.createVault({ wantMinted: 400, giveCollateral: 80, userKey: 'gov1' }); diff --git a/test/e2e/support.js b/test/e2e/support.js index ca61b857..78ec993e 100644 --- a/test/e2e/support.js +++ b/test/e2e/support.js @@ -5,6 +5,7 @@ import { FACUET_HEADERS, agoricNetworks, } from './test.utils'; +import { makeMarshal, Far } from './unmarshal.js'; const AGORIC_NET = Cypress.env('AGORIC_NET') || 'local'; const network = AGORIC_NET !== 'local' ? 'testnet' : 'local'; @@ -104,6 +105,69 @@ Cypress.Commands.add('verifyAuctionData', (propertyName, expectedValue) => { }); }); +const { fromCapData } = makeMarshal(undefined, (s, iface) => + Far(iface, { getSlot: () => s }), +); + +Cypress.Commands.add('pauseOldAuctioneer', () => { + const agopsOpts = { + env: { AGORIC_NET }, + timeout: COMMAND_TIMEOUT, + }; + const netConfig = + AGORIC_NET === 'local' + ? '' + : `-B https://${AGORIC_NET}.agoric.net/network-config`; + cy.exec( + `agoric follow ${netConfig} -lF :published.agoricNames.instance -o text`, + ).then(async ({ stdout }) => { + cy.wait(2 * 60 * 1000); + const byName = Object.fromEntries(fromCapData(JSON.parse(stdout))); + cy.expect(byName).to.have.property('auctioneer'); + cy.task('info', `Object is: ${JSON.stringify(byName)}`); + const auctioneer = byName.auctioneer; + // cy.expect(byName).to.have.property('auctioneer175'); + const oldAuctioneer = byName.auctioneer175; + + cy.exec( + `${agops} ec find-continuing-id --from gov1 --for 'charter member invitation'`, + agopsOpts, + ).then(({ stdout }) => { + const acceptId = stdout.trim(); + cy.expect(acceptId).to.be.a('string'); + cy.expect(acceptId).to.have.length.of.at.least(2); + + const changeFile = '/tmp/proposeChange.json'; + cy.expect(acceptId).to.be.a('string'); + cy.expect(acceptId).to.have.length.of.at.least(2); + cy.exec( + `${agops} auctioneer proposeParamChange --charterAcceptOfferId ${acceptId} --start-frequency 100000000000000`, + agopsOpts, + ).then(({ stdout }) => { + const offerSpec = JSON.parse(stdout); + cy.task('info', `offerSpec is: ${JSON.stringify(offerSpec)}`); + // cy.expect(offerSpec.slots[0]).to.equal(auctioneer.getSlot()); + offerSpec.slots[0] = oldAuctioneer.getSlot(); + cy.writeFile(changeFile, JSON.stringify(offerSpec), 'utf8'); + + const broadcastCommand = `${agops} perf satisfaction --executeOffer ${changeFile} --from gov1 --keyring-backend=test`; + + cy.exec(broadcastCommand, { + env: { AGORIC_NET }, + timeout: COMMAND_TIMEOUT, + failOnNonZeroExit: false, //@@@ + }).then(({ stdout, stderr }) => { + console.log('@@broadcast', { stdout, stderr }); + expect(stdout).not.to.contain('Error'); + }); + + cy.exec(`${agops} ec vote --send-from gov1 --forPosition 0`, agopsOpts); + cy.exec(`${agops} ec vote --send-from gov2 --forPosition 0`, agopsOpts); + }); + }); + }); +}); + Cypress.Commands.add('skipWhen', function (expression) { if (expression) { this.skip(); diff --git a/test/e2e/unmarshal.js b/test/e2e/unmarshal.js new file mode 100644 index 00000000..60eb52ae --- /dev/null +++ b/test/e2e/unmarshal.js @@ -0,0 +1,110 @@ +// @ts-check +'use strict'; + +const { + create, + entries, + fromEntries, + freeze, + keys, + setPrototypeOf, + prototype: objectPrototype, +} = Object; +const { isArray } = Array; + +const { freeze: harden } = Object; + +const sigilDoc = { + '!': 'escaped string', + '+': `non-negative bigint`, + '-': `negative bigint`, + '#': `manifest constant`, + '%': `symbol`, + $: `remotable`, + '&': `promise`, +}; +const sigils = keys(sigilDoc).join(''); + +/** @type {(obj: Record, f: (v: V) => U) => Record} */ +const objMap = (obj, f) => + fromEntries(entries(obj).map(([p, v]) => [f(p), f(v)])); + +export const makeMarshal = (_v2s, convertSlotToVal = (s, _i) => s) => { + const fromCapData = ({ body, slots }) => { + const recur = (v) => { + switch (typeof v) { + case 'boolean': + case 'number': + return v; + case 'string': + if (v === '') return v; + const sigil = v.slice(0, 1); + if (!sigils.includes(sigil)) return v; + switch (sigil) { + case '!': + return v.slice(1); + case '+': + return BigInt(v.slice(1)); + case '-': + return -BigInt(v.slice(1)); + case '$': { + const [ix, iface] = v.slice(1).split('.'); + return convertSlotToVal(slots[Number(ix)], iface); + } + case '#': + switch (v) { + case '#undefined': + return undefined; + case '#Infinity': + return Infinity; + case '#NaN': + return Infinity; + default: + throw RangeError(`Unexpected constant ${v}`); + } + case '%': + // TODO: @@asyncIterator + return Symbol.for(v.slice(1)); + default: + throw RangeError(`Unexpected sigil ${sigil}`); + } + case 'object': + if (v === null) return v; + if (isArray(v)) { + return freeze(v.map(recur)); + } + return freeze(objMap(v, recur)); + default: + throw RangeError(`Unexpected value type ${typeof v}`); + } + }; + const encoding = JSON.parse(body.replace(/^#/, '')); + return recur(encoding); + }; + + const toCapData = () => { + throw Error('not implemented'); + }; + + return harden({ + fromCapData, + unserialize: fromCapData, + toCapData, + serialize: toCapData, + }); +}; + +const PASS_STYLE = Symbol.for('passStyle'); +export const Far = (iface, methods) => { + const proto = freeze( + create(objectPrototype, { + [PASS_STYLE]: { value: 'remotable' }, + [Symbol.toStringTag]: { value: iface }, + }) + ); + setPrototypeOf(methods, proto); + freeze(methods); + return methods; +}; + +// export { makeUnmarshal };