@@ -2,89 +2,109 @@ import harden from '@agoric/harden';
2
2
import { assert , details } from '@agoric/assert' ;
3
3
import { mustBeComparable } from '@agoric/same-structure' ;
4
4
5
- import { arrayToObj } from './objArrayConversion' ;
5
+ import { arrayToObj , assertSubset } from './objArrayConversion' ;
6
6
7
- // cleanProposal checks the keys and values of the proposal, including
8
- // the keys and values of the internal objects. The proposal may have
9
- // the following keys: `give`, `want`, and `exit`. These keys may be
10
- // omitted in the `proposal` argument passed to cleanProposal, but
11
- // anything other than these keys is not allowed. The values of `give`
12
- // and `want` must be "amountKeywordRecords", meaning that the keys
13
- // must be keywords and the values must be amounts. The value of
14
- // `exit`, if present, must be a record of one of the following forms:
15
- // `{ waived: null }` `{ onDemand: null }` `{ afterDeadline: { timer
16
- // :Timer, deadline :Number } }
7
+ export const assertCapASCII = keyword => {
8
+ assert . typeof ( keyword , 'string' ) ;
9
+ const firstCapASCII = / ^ [ A - Z ] [ a - z A - Z 0 - 9 _ $ ] * $ / ;
10
+ assert (
11
+ firstCapASCII . test ( keyword ) ,
12
+ details `keyword ${ keyword } must be ascii and must start with a capital letter.` ,
13
+ ) ;
14
+ } ;
17
15
18
- // Assert that the keys of record, if present, are in expectedKeys.
19
- // Return the keys after asserting this.
20
- const checkKeys = ( expectedKeys , record ) => {
21
- // Assert that keys, if present, match expectedKeys.
16
+ // Assert that the keys of ` record` are all in `allowedKeys`. If a key
17
+ // of `record` is not in `allowedKeys`, throw an error. If a key in
18
+ // `allowedKeys` is not a key of record, we do not throw an error.
19
+ const assertKeysAllowed = ( allowedKeys , record ) => {
22
20
const keys = Object . getOwnPropertyNames ( record ) ;
23
- keys . forEach ( key => {
24
- assert . typeof ( key , 'string' ) ;
25
- assert (
26
- expectedKeys . includes ( key ) ,
27
- details `key ${ key } was not an expected key` ,
28
- ) ;
29
- } ) ;
21
+ assertSubset ( allowedKeys , keys ) ;
30
22
// assert that there are no symbol properties.
31
23
assert (
32
24
Object . getOwnPropertySymbols ( record ) . length === 0 ,
33
25
details `no symbol properties allowed` ,
34
26
) ;
35
- return keys ;
36
27
} ;
37
28
38
- const coerceAmountKeywordRecordValues = (
29
+ const cleanKeys = ( allowedKeys , record ) => {
30
+ assertKeysAllowed ( allowedKeys , record ) ;
31
+ return Object . getOwnPropertyNames ( record ) ;
32
+ } ;
33
+
34
+ export const getKeywords = keywordRecord =>
35
+ harden ( Object . getOwnPropertyNames ( keywordRecord ) ) ;
36
+
37
+ export const coerceAmountKeywordRecord = (
39
38
amountMathKeywordRecord ,
40
- validatedKeywords ,
39
+ allKeywords ,
41
40
allegedAmountKeywordRecord ,
42
41
) => {
42
+ const sparseKeywords = cleanKeys ( allKeywords , allegedAmountKeywordRecord ) ;
43
43
// Check that each value can be coerced using the amountMath indexed
44
44
// by keyword. `AmountMath.coerce` throws if coercion fails.
45
- const coercedAmounts = validatedKeywords . map ( keyword =>
45
+ const coercedAmounts = sparseKeywords . map ( keyword =>
46
46
amountMathKeywordRecord [ keyword ] . coerce (
47
47
allegedAmountKeywordRecord [ keyword ] ,
48
48
) ,
49
49
) ;
50
50
51
51
// Recreate the amountKeywordRecord with coercedAmounts.
52
- return arrayToObj ( coercedAmounts , validatedKeywords ) ;
52
+ return arrayToObj ( coercedAmounts , sparseKeywords ) ;
53
53
} ;
54
54
55
- export const coerceAmountKeywordRecord = (
56
- amountMathKeywordRecord ,
57
- keywords ,
58
- allegedAmountKeywordRecord ,
59
- ) => {
60
- const validatedKeywords = checkKeys ( keywords , allegedAmountKeywordRecord ) ;
61
- return coerceAmountKeywordRecordValues (
62
- amountMathKeywordRecord ,
63
- validatedKeywords ,
64
- allegedAmountKeywordRecord ,
55
+ export const cleanKeywords = keywordRecord => {
56
+ // `getOwnPropertyNames` returns all the non-symbol properties
57
+ // (both enumerable and non-enumerable).
58
+ const keywords = Object . getOwnPropertyNames ( keywordRecord ) ;
59
+
60
+ // Insist that there are no symbol properties.
61
+ assert (
62
+ Object . getOwnPropertySymbols ( keywordRecord ) . length === 0 ,
63
+ details `no symbol properties allowed` ,
65
64
) ;
65
+
66
+ // Assert all key characters are ascii and keys start with a
67
+ // capital letter.
68
+ keywords . forEach ( assertCapASCII ) ;
69
+
70
+ return keywords ;
66
71
} ;
67
72
68
- export const cleanProposal = ( keywords , amountMathKeywordRecord , proposal ) => {
69
- const expectedRootKeys = [ 'want' , 'give' , 'exit' ] ;
73
+ // cleanProposal checks the keys and values of the proposal, including
74
+ // the keys and values of the internal objects. The proposal may have
75
+ // the following keys: `give`, `want`, and `exit`. These keys may be
76
+ // omitted in the `proposal` argument passed to cleanProposal, but
77
+ // anything other than these keys is not allowed. The values of `give`
78
+ // and `want` must be "amountKeywordRecords", meaning that the keys
79
+ // must be keywords and the values must be amounts. The value of
80
+ // `exit`, if present, must be a record of one of the following forms:
81
+ // `{ waived: null }` `{ onDemand: null }` `{ afterDeadline: { timer
82
+ // :Timer, deadline :Number } }
83
+ export const cleanProposal = (
84
+ issuerKeywordRecord ,
85
+ amountMathKeywordRecord ,
86
+ proposal ,
87
+ ) => {
88
+ const rootKeysAllowed = [ 'want' , 'give' , 'exit' ] ;
70
89
mustBeComparable ( proposal ) ;
71
- checkKeys ( expectedRootKeys , proposal ) ;
90
+ assertKeysAllowed ( rootKeysAllowed , proposal ) ;
72
91
73
92
// We fill in the default values if the keys are undefined.
74
93
let { want = harden ( { } ) , give = harden ( { } ) } = proposal ;
75
94
const { exit = harden ( { onDemand : null } ) } = proposal ;
76
95
77
- want = coerceAmountKeywordRecord ( amountMathKeywordRecord , keywords , want ) ;
78
- give = coerceAmountKeywordRecord ( amountMathKeywordRecord , keywords , give ) ;
96
+ const allKeywords = getKeywords ( issuerKeywordRecord ) ;
97
+ want = coerceAmountKeywordRecord ( amountMathKeywordRecord , allKeywords , want ) ;
98
+ give = coerceAmountKeywordRecord ( amountMathKeywordRecord , allKeywords , give ) ;
79
99
80
100
// Check exit
81
101
assert (
82
102
Object . getOwnPropertyNames ( exit ) . length === 1 ,
83
103
details `exit ${ proposal . exit } should only have one key` ,
84
104
) ;
85
105
// We expect the single exit key to be one of the following:
86
- const expectedExitKeys = [ 'onDemand' , 'afterDeadline' , 'waived' ] ;
87
- const [ exitKey ] = checkKeys ( expectedExitKeys , exit ) ;
106
+ const allowedExitKeys = [ 'onDemand' , 'afterDeadline' , 'waived' ] ;
107
+ const [ exitKey ] = cleanKeys ( allowedExitKeys , exit ) ;
88
108
if ( exitKey === 'onDemand' || exitKey === 'waived' ) {
89
109
assert (
90
110
exit [ exitKey ] === null ,
@@ -93,37 +113,32 @@ export const cleanProposal = (keywords, amountMathKeywordRecord, proposal) => {
93
113
}
94
114
if ( exitKey === 'afterDeadline' ) {
95
115
const expectedAfterDeadlineKeys = [ 'timer' , 'deadline' ] ;
96
- checkKeys ( expectedAfterDeadlineKeys , exit . afterDeadline ) ;
116
+ assertKeysAllowed ( expectedAfterDeadlineKeys , exit . afterDeadline ) ;
117
+ assert (
118
+ exit . afterDeadline . timer !== undefined ,
119
+ details `timer must be defined` ,
120
+ ) ;
121
+ assert (
122
+ exit . afterDeadline . deadline !== undefined ,
123
+ details `deadline must be defined` ,
124
+ ) ;
97
125
// timers must have a 'setWakeup' function which takes a deadline
98
126
// and an object as arguments.
99
127
// TODO: document timer interface
100
128
// https://github.com/Agoric/agoric-sdk/issues/751
101
129
// TODO: how to check methods on presences?
102
130
}
103
131
104
- const hasPropDefined = ( obj , prop ) => obj [ prop ] !== undefined ;
132
+ // check that keyword is not in both 'want' and 'give'.
133
+ const wantKeywordSet = new Set ( Object . getOwnPropertyNames ( want ) ) ;
134
+ const giveKeywords = Object . getOwnPropertyNames ( give ) ;
105
135
106
- // Create an unfrozen version of 'want' in case we need to add
107
- // properties.
108
- const wantObj = { ...want } ;
109
-
110
- keywords . forEach ( keyword => {
111
- // check that keyword is not in both 'want' and 'give'.
112
- const wantHas = hasPropDefined ( wantObj , keyword ) ;
113
- const giveHas = hasPropDefined ( give , keyword ) ;
136
+ giveKeywords . forEach ( keyword => {
114
137
assert (
115
- ! ( wantHas && giveHas ) ,
138
+ ! wantKeywordSet . has ( keyword ) ,
116
139
details `a keyword cannot be in both 'want' and 'give'` ,
117
140
) ;
118
- // If keyword is in neither, fill in with a 'want' of empty.
119
- if ( ! ( wantHas || giveHas ) ) {
120
- wantObj [ keyword ] = amountMathKeywordRecord [ keyword ] . getEmpty ( ) ;
121
- }
122
141
} ) ;
123
142
124
- return harden ( {
125
- want : wantObj ,
126
- give,
127
- exit,
128
- } ) ;
143
+ return harden ( { want, give, exit } ) ;
129
144
} ;
0 commit comments