-
Notifications
You must be signed in to change notification settings - Fork 227
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: fix Zoe bug in which offer safety can be violated (#1115)
* test: demonstrate an attack by a zoe contract to be fixed in facets The bug was introduced in "filter proposal give and want by sparseKeywords" #1076. It allows a grifter contract (demonstrated here) to slip assets away from a victim one keyword at a time. The victim is left with neither their give or their want. The solution in facets is to reinstate the insistance that offer safety be checked against all keywords. We provide the feature that #1076 was aiming for by allowing reallocations that don't impact keywords in an offer's proposal. * fix: Fix the Zoe offer-safety bug demonstrated by the grifter attack The fix is to insist that all offers retain offer safety across all keywords. The desired ability to rearrange values outside of the offer's keywords can be satisfied by explicitly allowing rearrangements that don't impact any of the proposal's keywords. This commit includes the bug fix but not a test that demonstrates the vulnerability. * test: correct the test to pass now that the bug is fixed. * fix: run offer safety check over the entire new allocation, not just the sparse keywords version. Co-authored-by: Kate Sills <[email protected]>
- Loading branch information
1 parent
b8d462e
commit 39d6ae2
Showing
6 changed files
with
164 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// @ts-check | ||
|
||
import harden from '@agoric/harden'; | ||
|
||
// Eventually will be importable from '@agoric/zoe-contract-support' | ||
import { makeZoeHelpers } from '../../../src/contractSupport'; | ||
|
||
/** @typedef {import('../zoe').ContractFacet} ContractFacet */ | ||
|
||
// zcf is the Zoe Contract Facet, i.e. the contract-facing API of Zoe | ||
export const makeContract = harden( | ||
/** @param {ContractFacet} zcf */ zcf => { | ||
const { assertKeywords, checkHook } = makeZoeHelpers(zcf); | ||
assertKeywords(harden(['Asset', 'Price'])); | ||
|
||
const makeAccompliceInvite = firstOfferHandle => { | ||
const { | ||
proposal: { want: wantProposal }, | ||
} = zcf.getOffer(firstOfferHandle); | ||
|
||
return zcf.makeInvitation( | ||
offerHandle => { | ||
const firstHandleAlloc = zcf.getCurrentAllocation(firstOfferHandle); | ||
// safe because it doesn't change give, so they can still get a refund | ||
const vicProposal = { Price: firstHandleAlloc.Price }; | ||
const stepOne = [wantProposal, vicProposal]; | ||
// safe because it doesn't change want, so winningsOK looks true | ||
const offerHandles = [firstOfferHandle, offerHandle]; | ||
zcf.reallocate(offerHandles, stepOne, ['Price']); | ||
zcf.complete(harden(offerHandles)); | ||
}, | ||
'tantalizing offer', | ||
harden({ | ||
customProperties: { | ||
Price: wantProposal.Price, | ||
}, | ||
}), | ||
); | ||
}; | ||
|
||
const firstOfferExpected = harden({ | ||
want: { Price: null }, | ||
}); | ||
|
||
return harden({ | ||
invite: zcf.makeInvitation( | ||
checkHook(makeAccompliceInvite, firstOfferExpected), | ||
'firstOffer', | ||
), | ||
}); | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import { test } from 'tape-promise/tape'; | ||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import bundleSource from '@agoric/bundle-source'; | ||
|
||
import harden from '@agoric/harden'; | ||
|
||
import { makeZoe } from '../../..'; | ||
// TODO: Remove setupBasicMints and rename setupBasicMints2 | ||
import { setup } from '../setupBasicMints'; | ||
|
||
const grifterRoot = `${__dirname}/grifter`; | ||
|
||
test('zoe - grifter tries to steal; prevented by offer safety', async t => { | ||
t.plan(1); | ||
// Setup zoe and mints | ||
const { moola, moolaR, moolaMint, bucksR, bucks } = setup(); | ||
const zoe = makeZoe({ require }); | ||
// Pack the contract. | ||
const { source, moduleFormat } = await bundleSource(grifterRoot); | ||
const installationHandle = zoe.install(source, moduleFormat); | ||
|
||
const issuerKeywordRecord = harden({ | ||
Asset: bucksR.issuer, | ||
Price: moolaR.issuer, | ||
}); | ||
|
||
const malloryInvite = zoe.makeInstance( | ||
installationHandle, | ||
issuerKeywordRecord, | ||
); | ||
|
||
// Mallory doesn't need any money | ||
const malloryProposal = harden({ | ||
want: { Price: moola(37) }, | ||
}); | ||
const { outcome: vicInviteP } = await zoe.offer( | ||
malloryInvite, | ||
malloryProposal, | ||
harden({}), | ||
); | ||
|
||
const vicMoolaPayment = moolaMint.mintPayment(moola(37)); | ||
const vicProposal = harden({ | ||
give: { Price: moola(37) }, | ||
want: { Asset: bucks(24) }, | ||
exit: { onDemand: null }, | ||
}); | ||
const vicPayments = { Price: vicMoolaPayment }; | ||
const { outcome: vicOutcomeP } = await zoe.offer( | ||
vicInviteP, | ||
vicProposal, | ||
vicPayments, | ||
); | ||
|
||
t.rejects( | ||
vicOutcomeP, | ||
/The proposed reallocation was not offer safe/, | ||
`vicOffer is rejected`, | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters