@openzeppelin/wizard-stellar 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/add-upgradeable.d.ts.map +1 -1
- package/dist/add-upgradeable.js +15 -14
- package/dist/add-upgradeable.js.map +1 -1
- package/dist/api.d.ts +5 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +7 -1
- package/dist/api.js.map +1 -1
- package/dist/build-generic.d.ts +4 -0
- package/dist/build-generic.d.ts.map +1 -1
- package/dist/build-generic.js +3 -0
- package/dist/build-generic.js.map +1 -1
- package/dist/contract.d.ts +3 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +4 -0
- package/dist/contract.js.map +1 -1
- package/dist/fungible.d.ts +3 -1
- package/dist/fungible.d.ts.map +1 -1
- package/dist/fungible.js +80 -27
- package/dist/fungible.js.map +1 -1
- package/dist/generate/fungible.d.ts.map +1 -1
- package/dist/generate/fungible.js +2 -0
- package/dist/generate/fungible.js.map +1 -1
- package/dist/generate/governor.d.ts +3 -0
- package/dist/generate/governor.d.ts.map +1 -0
- package/dist/generate/governor.js +24 -0
- package/dist/generate/governor.js.map +1 -0
- package/dist/generate/non-fungible.d.ts.map +1 -1
- package/dist/generate/non-fungible.js +1 -0
- package/dist/generate/non-fungible.js.map +1 -1
- package/dist/generate/sources.d.ts.map +1 -1
- package/dist/generate/sources.js +6 -0
- package/dist/generate/sources.js.map +1 -1
- package/dist/generate/stablecoin.d.ts.map +1 -1
- package/dist/generate/stablecoin.js +2 -0
- package/dist/generate/stablecoin.js.map +1 -1
- package/dist/governor.d.ts +17 -0
- package/dist/governor.d.ts.map +1 -0
- package/dist/governor.js +378 -0
- package/dist/governor.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/kind.js +1 -0
- package/dist/kind.js.map +1 -1
- package/dist/non-fungible.d.ts +1 -0
- package/dist/non-fungible.d.ts.map +1 -1
- package/dist/non-fungible.js +68 -7
- package/dist/non-fungible.js.map +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +6 -1
- package/dist/print.js.map +1 -1
- package/dist/stablecoin.d.ts.map +1 -1
- package/dist/stablecoin.js +7 -0
- package/dist/stablecoin.js.map +1 -1
- package/dist/test.js +3 -0
- package/dist/test.js.map +1 -1
- package/dist/utils/compile-test.d.ts +2 -2
- package/dist/utils/compile-test.d.ts.map +1 -1
- package/dist/utils/compile-test.js +54 -3
- package/dist/utils/compile-test.js.map +1 -1
- package/dist/utils/version.d.ts +4 -4
- package/dist/utils/version.js +3 -3
- package/dist/zip-rust.d.ts.map +1 -1
- package/dist/zip-rust.js +3 -2
- package/dist/zip-rust.js.map +1 -1
- package/dist/zip-shared.d.ts +3 -4
- package/dist/zip-shared.d.ts.map +1 -1
- package/dist/zip-shared.js +20 -7
- package/dist/zip-shared.js.map +1 -1
- package/package.json +1 -1
- package/src/add-upgradeable.ts +15 -16
- package/src/api.ts +15 -2
- package/src/build-generic.ts +6 -0
- package/src/contract.ts +6 -0
- package/src/fungible.ts +106 -27
- package/src/generate/fungible.ts +2 -0
- package/src/generate/governor.ts +24 -0
- package/src/generate/non-fungible.ts +1 -0
- package/src/generate/sources.ts +7 -0
- package/src/generate/stablecoin.ts +2 -0
- package/src/governor.ts +425 -0
- package/src/index.ts +2 -0
- package/src/kind.ts +1 -0
- package/src/non-fungible.ts +73 -3
- package/src/print.ts +6 -1
- package/src/stablecoin.ts +8 -0
- package/src/test.ts +4 -0
- package/src/utils/compile-test.ts +84 -5
- package/src/utils/version.ts +3 -3
- package/src/zip-rust.ts +4 -2
- package/src/zip-shared.ts +17 -6
package/src/governor.ts
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import type { CommonContractOptions } from './common-options';
|
|
2
|
+
import { contractDefaults as commonDefaults, getSelfArg, withCommonContractDefaults } from './common-options';
|
|
3
|
+
import { ContractBuilder } from './contract';
|
|
4
|
+
import { printContract } from './print';
|
|
5
|
+
import { setAccessControl } from './set-access-control';
|
|
6
|
+
import { addUpgradeable } from './add-upgradeable';
|
|
7
|
+
import { setInfo } from './set-info';
|
|
8
|
+
import { toByteArray, toUint } from './utils/convert-strings';
|
|
9
|
+
import { defineFunctions } from './utils/define-functions';
|
|
10
|
+
|
|
11
|
+
export const defaults: Required<GovernorOptions> = {
|
|
12
|
+
name: 'MyGovernor',
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
votingDelay: '17000',
|
|
15
|
+
votingPeriod: '120000',
|
|
16
|
+
proposalThreshold: '100',
|
|
17
|
+
quorum: '500',
|
|
18
|
+
upgradeable: false,
|
|
19
|
+
timelock: false,
|
|
20
|
+
access: commonDefaults.access,
|
|
21
|
+
info: commonDefaults.info,
|
|
22
|
+
explicitImplementations: commonDefaults.explicitImplementations,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export function printGovernor(opts: GovernorOptions = defaults): string {
|
|
26
|
+
return printContract(buildGovernor(opts));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface GovernorOptions extends CommonContractOptions {
|
|
30
|
+
name: string;
|
|
31
|
+
version?: string;
|
|
32
|
+
votingDelay?: string;
|
|
33
|
+
votingPeriod?: string;
|
|
34
|
+
proposalThreshold?: string;
|
|
35
|
+
quorum?: string;
|
|
36
|
+
upgradeable?: boolean;
|
|
37
|
+
timelock?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isAccessControlRequired(opts: Partial<GovernorOptions>): boolean {
|
|
41
|
+
return opts.upgradeable === true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function withDefaults(opts: GovernorOptions): Required<GovernorOptions> {
|
|
45
|
+
return {
|
|
46
|
+
...opts,
|
|
47
|
+
...withCommonContractDefaults(opts),
|
|
48
|
+
version: opts.version ?? defaults.version,
|
|
49
|
+
votingDelay: opts.votingDelay ?? defaults.votingDelay,
|
|
50
|
+
votingPeriod: opts.votingPeriod ?? defaults.votingPeriod,
|
|
51
|
+
proposalThreshold: opts.proposalThreshold ?? defaults.proposalThreshold,
|
|
52
|
+
quorum: opts.quorum ?? defaults.quorum,
|
|
53
|
+
upgradeable: opts.upgradeable ?? defaults.upgradeable,
|
|
54
|
+
timelock: opts.timelock ?? defaults.timelock,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function buildGovernor(opts: GovernorOptions): ContractBuilder {
|
|
59
|
+
const allOpts = withDefaults(opts);
|
|
60
|
+
const c = new ContractBuilder(allOpts.name);
|
|
61
|
+
|
|
62
|
+
addBase(
|
|
63
|
+
c,
|
|
64
|
+
toByteArray(allOpts.name),
|
|
65
|
+
toByteArray(allOpts.version),
|
|
66
|
+
toUint(allOpts.votingDelay, 'votingDelay', 'u32'),
|
|
67
|
+
toUint(allOpts.votingPeriod, 'votingPeriod', 'u32'),
|
|
68
|
+
toUint(allOpts.proposalThreshold, 'proposalThreshold', 'u128'),
|
|
69
|
+
toUint(allOpts.quorum, 'quorum', 'u128'),
|
|
70
|
+
allOpts.timelock,
|
|
71
|
+
allOpts.explicitImplementations,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (allOpts.upgradeable) {
|
|
75
|
+
addUpgradeable(c, allOpts.access, allOpts.explicitImplementations);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setAccessControl(c, allOpts.access, allOpts.explicitImplementations);
|
|
79
|
+
setInfo(c, allOpts.info);
|
|
80
|
+
|
|
81
|
+
return c;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function addBase(
|
|
85
|
+
c: ContractBuilder,
|
|
86
|
+
name: string,
|
|
87
|
+
version: string,
|
|
88
|
+
votingDelay: bigint,
|
|
89
|
+
votingPeriod: bigint,
|
|
90
|
+
proposalThreshold: bigint,
|
|
91
|
+
quorum: bigint,
|
|
92
|
+
timelock: boolean,
|
|
93
|
+
explicitImplementations: boolean,
|
|
94
|
+
) {
|
|
95
|
+
c.addUseClause('stellar_governance::governor', 'Governor');
|
|
96
|
+
c.addUseClause('stellar_governance::governor', 'ProposalState');
|
|
97
|
+
c.addUseClause('stellar_governance::governor', 'self', { alias: 'governor' });
|
|
98
|
+
c.addUseClause('soroban_sdk', 'contract');
|
|
99
|
+
c.addUseClause('soroban_sdk', 'contractimpl');
|
|
100
|
+
c.addUseClause('soroban_sdk', 'Address');
|
|
101
|
+
c.addUseClause('soroban_sdk', 'BytesN');
|
|
102
|
+
c.addUseClause('soroban_sdk', 'Env');
|
|
103
|
+
c.addUseClause('soroban_sdk', 'String');
|
|
104
|
+
c.addUseClause('soroban_sdk', 'Symbol');
|
|
105
|
+
c.addUseClause('soroban_sdk', 'Val');
|
|
106
|
+
c.addUseClause('soroban_sdk', 'Vec');
|
|
107
|
+
|
|
108
|
+
c.addConstructorArgument({ name: 'token_contract', type: 'Address' });
|
|
109
|
+
c.addConstructorCode(`governor::set_name(e, String::from_str(e, "${name}"));`);
|
|
110
|
+
c.addConstructorCode(`governor::set_version(e, String::from_str(e, "${version}"));`);
|
|
111
|
+
c.addConstructorCode(`governor::set_voting_delay(e, ${votingDelay});`);
|
|
112
|
+
c.addConstructorCode(`governor::set_voting_period(e, ${votingPeriod});`);
|
|
113
|
+
c.addConstructorCode(`governor::set_proposal_threshold(e, ${proposalThreshold});`);
|
|
114
|
+
c.addConstructorCode(`governor::set_quorum(e, ${quorum});`);
|
|
115
|
+
c.addConstructorCode('governor::set_token_contract(e, &token_contract);');
|
|
116
|
+
|
|
117
|
+
const governorTrait = {
|
|
118
|
+
traitName: 'Governor',
|
|
119
|
+
structName: c.name,
|
|
120
|
+
tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Custom impls must be added before the defaults so the deduplication keeps
|
|
124
|
+
// the custom version when a default-method override exists (e.g. timelock's
|
|
125
|
+
// `proposals_need_queuing`/`queue` overriding the trait defaults).
|
|
126
|
+
if (timelock) {
|
|
127
|
+
addTimelock(c, governorTrait);
|
|
128
|
+
} else {
|
|
129
|
+
c.addTraitFunction(governorTrait, functions.execute);
|
|
130
|
+
c.addTraitFunction(governorTrait, functions.cancel);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (explicitImplementations) {
|
|
134
|
+
c.addTraitForEachFunctions(governorTrait, governorTraitDefaultFunctions);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function addTimelock(c: ContractBuilder, governorTrait: { traitName: string; structName: string; tags: string[] }) {
|
|
139
|
+
c.addUseClause('soroban_sdk', 'contracttype');
|
|
140
|
+
c.addUseClause('soroban_sdk', 'IntoVal');
|
|
141
|
+
c.addUseClause('stellar_governance::timelock', 'TimelockClient');
|
|
142
|
+
|
|
143
|
+
c.addTopLevelDeclaration([
|
|
144
|
+
'#[contracttype]',
|
|
145
|
+
'enum GovernorTimelockKey {',
|
|
146
|
+
' Timelock,',
|
|
147
|
+
' OperationID(BytesN<32>),',
|
|
148
|
+
'}',
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
c.addConstructorArgument({ name: 'timelock_contract', type: 'Address' });
|
|
152
|
+
c.addConstructorCode('e.storage().instance().set(&GovernorTimelockKey::Timelock, &timelock_contract);');
|
|
153
|
+
|
|
154
|
+
c.addFreeFunction(timelockFreeFunctions.timelock);
|
|
155
|
+
|
|
156
|
+
c.addTraitFunction(governorTrait, timelockFunctions.proposals_need_queuing);
|
|
157
|
+
c.addTraitFunction(governorTrait, timelockFunctions.queue);
|
|
158
|
+
c.addTraitFunction(governorTrait, timelockFunctions.execute);
|
|
159
|
+
c.addTraitFunction(governorTrait, timelockFunctions.cancel);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const functions = defineFunctions({
|
|
163
|
+
execute: {
|
|
164
|
+
args: [
|
|
165
|
+
getSelfArg(),
|
|
166
|
+
{ name: 'targets', type: 'Vec<Address>' },
|
|
167
|
+
{ name: 'functions', type: 'Vec<Symbol>' },
|
|
168
|
+
{ name: 'args', type: 'Vec<Vec<Val>>' },
|
|
169
|
+
{ name: 'description_hash', type: 'BytesN<32>' },
|
|
170
|
+
{ name: 'executor', type: 'Address' },
|
|
171
|
+
],
|
|
172
|
+
returns: 'BytesN<32>',
|
|
173
|
+
code: [
|
|
174
|
+
'executor.require_auth()',
|
|
175
|
+
'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
|
|
176
|
+
'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
|
|
177
|
+
'let quorum = Self::quorum(e, snapshot)',
|
|
178
|
+
'governor::execute(e, targets, functions, args, &description_hash, Self::proposals_need_queuing(e), quorum)',
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
cancel: {
|
|
182
|
+
args: [
|
|
183
|
+
getSelfArg(),
|
|
184
|
+
{ name: 'targets', type: 'Vec<Address>' },
|
|
185
|
+
{ name: 'functions', type: 'Vec<Symbol>' },
|
|
186
|
+
{ name: 'args', type: 'Vec<Vec<Val>>' },
|
|
187
|
+
{ name: 'description_hash', type: 'BytesN<32>' },
|
|
188
|
+
{ name: 'operator', type: 'Address' },
|
|
189
|
+
],
|
|
190
|
+
returns: 'BytesN<32>',
|
|
191
|
+
code: [
|
|
192
|
+
'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
|
|
193
|
+
'let proposer = governor::get_proposal_proposer(e, &proposal_id)',
|
|
194
|
+
'assert!(operator == proposer)',
|
|
195
|
+
'operator.require_auth()',
|
|
196
|
+
'governor::cancel(e, targets, functions, args, &description_hash)',
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const timelockFreeFunctions = defineFunctions({
|
|
202
|
+
timelock: {
|
|
203
|
+
args: [getSelfArg()],
|
|
204
|
+
returns: 'Address',
|
|
205
|
+
code: ['e.storage().instance().get(&GovernorTimelockKey::Timelock).expect("timelock not set")'],
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const timelockFunctions = defineFunctions({
|
|
210
|
+
proposals_need_queuing: {
|
|
211
|
+
args: [{ name: '_e', type: '&Env' }],
|
|
212
|
+
returns: 'bool',
|
|
213
|
+
code: ['true'],
|
|
214
|
+
},
|
|
215
|
+
queue: {
|
|
216
|
+
args: [
|
|
217
|
+
getSelfArg(),
|
|
218
|
+
{ name: 'targets', type: 'Vec<Address>' },
|
|
219
|
+
{ name: 'functions', type: 'Vec<Symbol>' },
|
|
220
|
+
{ name: 'args', type: 'Vec<Vec<Val>>' },
|
|
221
|
+
{ name: 'description_hash', type: 'BytesN<32>' },
|
|
222
|
+
{ name: 'eta', type: 'u32' },
|
|
223
|
+
{ name: '_operator', type: 'Address' },
|
|
224
|
+
],
|
|
225
|
+
returns: 'BytesN<32>',
|
|
226
|
+
code: [
|
|
227
|
+
'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
|
|
228
|
+
'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
|
|
229
|
+
'let quorum = Self::quorum(e, snapshot)',
|
|
230
|
+
'let proposal_id = governor::queue(e, targets.clone(), functions.clone(), args.clone(), &description_hash, eta, quorum)',
|
|
231
|
+
'let timelock = Self::timelock(e)',
|
|
232
|
+
'let delay = eta.saturating_sub(e.ledger().sequence())',
|
|
233
|
+
'let execute_args: Vec<Val> = (targets, functions, args, description_hash.clone(), timelock.clone()).into_val(e)',
|
|
234
|
+
'let timelock_client = TimelockClient::new(e, &timelock)',
|
|
235
|
+
'let zero_predecessor = BytesN::from_array(e, &[0u8; 32])',
|
|
236
|
+
'let op_id = timelock_client.schedule(&e.current_contract_address(), &Symbol::new(e, "execute"), &execute_args, &zero_predecessor, &description_hash, &delay, &e.current_contract_address())',
|
|
237
|
+
'e.storage().persistent().set(&GovernorTimelockKey::OperationID(proposal_id.clone()), &op_id);',
|
|
238
|
+
'proposal_id',
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
execute: {
|
|
242
|
+
args: [
|
|
243
|
+
getSelfArg(),
|
|
244
|
+
{ name: 'targets', type: 'Vec<Address>' },
|
|
245
|
+
{ name: 'functions', type: 'Vec<Symbol>' },
|
|
246
|
+
{ name: 'args', type: 'Vec<Vec<Val>>' },
|
|
247
|
+
{ name: 'description_hash', type: 'BytesN<32>' },
|
|
248
|
+
{ name: 'executor', type: 'Address' },
|
|
249
|
+
],
|
|
250
|
+
returns: 'BytesN<32>',
|
|
251
|
+
code: [
|
|
252
|
+
'let timelock = Self::timelock(e)',
|
|
253
|
+
'assert!(executor == timelock)',
|
|
254
|
+
'executor.require_auth()',
|
|
255
|
+
'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
|
|
256
|
+
'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
|
|
257
|
+
'let quorum = Self::quorum(e, snapshot)',
|
|
258
|
+
'governor::execute(e, targets, functions, args, &description_hash, Self::proposals_need_queuing(e), quorum)',
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
cancel: {
|
|
262
|
+
args: [
|
|
263
|
+
getSelfArg(),
|
|
264
|
+
{ name: 'targets', type: 'Vec<Address>' },
|
|
265
|
+
{ name: 'functions', type: 'Vec<Symbol>' },
|
|
266
|
+
{ name: 'args', type: 'Vec<Vec<Val>>' },
|
|
267
|
+
{ name: 'description_hash', type: 'BytesN<32>' },
|
|
268
|
+
{ name: 'operator', type: 'Address' },
|
|
269
|
+
],
|
|
270
|
+
returns: 'BytesN<32>',
|
|
271
|
+
code: [
|
|
272
|
+
'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
|
|
273
|
+
'let proposer = governor::get_proposal_proposer(e, &proposal_id)',
|
|
274
|
+
'assert!(operator == proposer)',
|
|
275
|
+
'operator.require_auth()',
|
|
276
|
+
'if let Some(op_id) = e.storage().persistent().get::<_, BytesN<32>>(&GovernorTimelockKey::OperationID(proposal_id)) {',
|
|
277
|
+
' let timelock = Self::timelock(e);',
|
|
278
|
+
' let timelock_client = TimelockClient::new(e, &timelock);',
|
|
279
|
+
' timelock_client.cancel(&op_id, &e.current_contract_address());',
|
|
280
|
+
'}',
|
|
281
|
+
'governor::cancel(e, targets, functions, args, &description_hash)',
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Explicit-trait variants for every default method on the `Governor` trait.
|
|
287
|
+
// When `explicitImplementations` is enabled, these are emitted as overrides
|
|
288
|
+
// that mirror the upstream defaults — letting users see and customize each
|
|
289
|
+
// method directly. Order matches the trait definition for readability.
|
|
290
|
+
const governorTraitDefaultFunctions = defineFunctions({
|
|
291
|
+
name: {
|
|
292
|
+
args: [getSelfArg()],
|
|
293
|
+
returns: 'String',
|
|
294
|
+
code: ['governor::get_name(e)'],
|
|
295
|
+
},
|
|
296
|
+
version: {
|
|
297
|
+
args: [getSelfArg()],
|
|
298
|
+
returns: 'String',
|
|
299
|
+
code: ['governor::get_version(e)'],
|
|
300
|
+
},
|
|
301
|
+
voting_delay: {
|
|
302
|
+
args: [getSelfArg()],
|
|
303
|
+
returns: 'u32',
|
|
304
|
+
code: ['governor::get_voting_delay(e)'],
|
|
305
|
+
},
|
|
306
|
+
voting_period: {
|
|
307
|
+
args: [getSelfArg()],
|
|
308
|
+
returns: 'u32',
|
|
309
|
+
code: ['governor::get_voting_period(e)'],
|
|
310
|
+
},
|
|
311
|
+
proposal_threshold: {
|
|
312
|
+
args: [getSelfArg()],
|
|
313
|
+
returns: 'u128',
|
|
314
|
+
code: ['governor::get_proposal_threshold(e)'],
|
|
315
|
+
},
|
|
316
|
+
get_token_contract: {
|
|
317
|
+
args: [getSelfArg()],
|
|
318
|
+
returns: 'Address',
|
|
319
|
+
code: ['governor::get_token_contract(e)'],
|
|
320
|
+
},
|
|
321
|
+
counting_mode: {
|
|
322
|
+
args: [getSelfArg()],
|
|
323
|
+
returns: 'Symbol',
|
|
324
|
+
code: ['governor::counting_mode(e)'],
|
|
325
|
+
},
|
|
326
|
+
has_voted: {
|
|
327
|
+
args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }, { name: 'account', type: 'Address' }],
|
|
328
|
+
returns: 'bool',
|
|
329
|
+
code: ['governor::has_voted(e, &proposal_id, &account)'],
|
|
330
|
+
},
|
|
331
|
+
quorum: {
|
|
332
|
+
args: [getSelfArg(), { name: 'ledger', type: 'u32' }],
|
|
333
|
+
returns: 'u128',
|
|
334
|
+
code: ['governor::get_quorum(e, ledger)'],
|
|
335
|
+
},
|
|
336
|
+
proposal_state: {
|
|
337
|
+
args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }],
|
|
338
|
+
returns: 'ProposalState',
|
|
339
|
+
code: [
|
|
340
|
+
'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
|
|
341
|
+
'let quorum = Self::quorum(e, snapshot)',
|
|
342
|
+
'governor::get_proposal_state(e, &proposal_id, quorum)',
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
proposal_snapshot: {
|
|
346
|
+
args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }],
|
|
347
|
+
returns: 'u32',
|
|
348
|
+
code: ['governor::get_proposal_snapshot(e, &proposal_id)'],
|
|
349
|
+
},
|
|
350
|
+
proposal_deadline: {
|
|
351
|
+
args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }],
|
|
352
|
+
returns: 'u32',
|
|
353
|
+
code: ['governor::get_proposal_deadline(e, &proposal_id)'],
|
|
354
|
+
},
|
|
355
|
+
proposal_proposer: {
|
|
356
|
+
args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }],
|
|
357
|
+
returns: 'Address',
|
|
358
|
+
code: ['governor::get_proposal_proposer(e, &proposal_id)'],
|
|
359
|
+
},
|
|
360
|
+
get_proposal_id: {
|
|
361
|
+
args: [
|
|
362
|
+
getSelfArg(),
|
|
363
|
+
{ name: 'targets', type: 'Vec<Address>' },
|
|
364
|
+
{ name: 'functions', type: 'Vec<Symbol>' },
|
|
365
|
+
{ name: 'args', type: 'Vec<Vec<Val>>' },
|
|
366
|
+
{ name: 'description_hash', type: 'BytesN<32>' },
|
|
367
|
+
],
|
|
368
|
+
returns: 'BytesN<32>',
|
|
369
|
+
code: ['governor::hash_proposal(e, &targets, &functions, &args, &description_hash)'],
|
|
370
|
+
},
|
|
371
|
+
propose: {
|
|
372
|
+
args: [
|
|
373
|
+
getSelfArg(),
|
|
374
|
+
{ name: 'targets', type: 'Vec<Address>' },
|
|
375
|
+
{ name: 'functions', type: 'Vec<Symbol>' },
|
|
376
|
+
{ name: 'args', type: 'Vec<Vec<Val>>' },
|
|
377
|
+
{ name: 'description', type: 'String' },
|
|
378
|
+
{ name: 'proposer', type: 'Address' },
|
|
379
|
+
],
|
|
380
|
+
returns: 'BytesN<32>',
|
|
381
|
+
code: ['proposer.require_auth()', 'governor::propose(e, targets, functions, args, description, &proposer)'],
|
|
382
|
+
},
|
|
383
|
+
cast_vote: {
|
|
384
|
+
args: [
|
|
385
|
+
getSelfArg(),
|
|
386
|
+
{ name: 'proposal_id', type: 'BytesN<32>' },
|
|
387
|
+
{ name: 'vote_type', type: 'u32' },
|
|
388
|
+
{ name: 'reason', type: 'String' },
|
|
389
|
+
{ name: 'voter', type: 'Address' },
|
|
390
|
+
],
|
|
391
|
+
returns: 'u128',
|
|
392
|
+
code: [
|
|
393
|
+
'voter.require_auth()',
|
|
394
|
+
'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
|
|
395
|
+
'let quorum = Self::quorum(e, snapshot)',
|
|
396
|
+
'governor::cast_vote(e, &proposal_id, vote_type, &reason, &voter, quorum)',
|
|
397
|
+
],
|
|
398
|
+
},
|
|
399
|
+
queue: {
|
|
400
|
+
args: [
|
|
401
|
+
getSelfArg(),
|
|
402
|
+
{ name: 'targets', type: 'Vec<Address>' },
|
|
403
|
+
{ name: 'functions', type: 'Vec<Symbol>' },
|
|
404
|
+
{ name: 'args', type: 'Vec<Vec<Val>>' },
|
|
405
|
+
{ name: 'description_hash', type: 'BytesN<32>' },
|
|
406
|
+
{ name: 'eta', type: 'u32' },
|
|
407
|
+
{ name: '_operator', type: 'Address' },
|
|
408
|
+
],
|
|
409
|
+
returns: 'BytesN<32>',
|
|
410
|
+
code: [
|
|
411
|
+
'if !Self::proposals_need_queuing(e) {',
|
|
412
|
+
' soroban_sdk::panic_with_error!(e, governor::GovernorError::QueueNotEnabled);',
|
|
413
|
+
'}',
|
|
414
|
+
'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
|
|
415
|
+
'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
|
|
416
|
+
'let quorum = Self::quorum(e, snapshot)',
|
|
417
|
+
'governor::queue(e, targets, functions, args, &description_hash, eta, quorum)',
|
|
418
|
+
],
|
|
419
|
+
},
|
|
420
|
+
proposals_need_queuing: {
|
|
421
|
+
args: [{ name: '_e', type: '&Env' }],
|
|
422
|
+
returns: 'bool',
|
|
423
|
+
code: ['false'],
|
|
424
|
+
},
|
|
425
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -22,9 +22,11 @@ export { sanitizeKind } from './kind';
|
|
|
22
22
|
export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version';
|
|
23
23
|
|
|
24
24
|
export { fungible } from './api';
|
|
25
|
+
export { governor } from './api';
|
|
25
26
|
export { nonFungible } from './api';
|
|
26
27
|
export { stablecoin } from './api';
|
|
27
28
|
|
|
28
29
|
export type { FungibleOptions } from './fungible';
|
|
30
|
+
export type { GovernorOptions } from './governor';
|
|
29
31
|
export type { NonFungibleOptions } from './non-fungible';
|
|
30
32
|
export type { StablecoinOptions } from './stablecoin';
|
package/src/kind.ts
CHANGED
package/src/non-fungible.ts
CHANGED
|
@@ -18,6 +18,7 @@ export const defaults: Required<NonFungibleOptions> = {
|
|
|
18
18
|
symbol: 'MTK',
|
|
19
19
|
tokenUri: 'https://www.mytoken.com',
|
|
20
20
|
burnable: false,
|
|
21
|
+
votes: false,
|
|
21
22
|
enumerable: false,
|
|
22
23
|
consecutive: false,
|
|
23
24
|
pausable: false,
|
|
@@ -38,6 +39,7 @@ export interface NonFungibleOptions extends CommonContractOptions {
|
|
|
38
39
|
symbol: string;
|
|
39
40
|
tokenUri?: string;
|
|
40
41
|
burnable?: boolean;
|
|
42
|
+
votes?: boolean;
|
|
41
43
|
enumerable?: boolean;
|
|
42
44
|
consecutive?: boolean;
|
|
43
45
|
pausable?: boolean;
|
|
@@ -52,6 +54,7 @@ function withDefaults(opts: NonFungibleOptions): Required<NonFungibleOptions> {
|
|
|
52
54
|
...withCommonContractDefaults(opts),
|
|
53
55
|
tokenUri: opts.tokenUri ?? defaults.tokenUri,
|
|
54
56
|
burnable: opts.burnable ?? defaults.burnable,
|
|
57
|
+
votes: opts.votes ?? defaults.votes,
|
|
55
58
|
consecutive: opts.consecutive ?? defaults.consecutive,
|
|
56
59
|
enumerable: opts.enumerable ?? defaults.enumerable,
|
|
57
60
|
pausable: opts.pausable ?? defaults.pausable,
|
|
@@ -87,6 +90,16 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract {
|
|
|
87
90
|
errors.sequential = 'Sequential minting cannot be used with Consecutive extension';
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
if (allOpts.votes && allOpts.enumerable) {
|
|
94
|
+
errors.votes = 'Votes extension cannot be used with Enumerable extension';
|
|
95
|
+
errors.enumerable = 'Enumerable extension cannot be used with Votes extension';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (allOpts.votes && allOpts.consecutive) {
|
|
99
|
+
errors.votes = 'Votes extension cannot be used with Consecutive extension';
|
|
100
|
+
errors.consecutive = 'Consecutive extension cannot be used with Votes extension';
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
if (Object.keys(errors).length > 0) {
|
|
91
104
|
throw new OptionsError(errors);
|
|
92
105
|
}
|
|
@@ -96,6 +109,7 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract {
|
|
|
96
109
|
toByteArray(allOpts.name),
|
|
97
110
|
toByteArray(allOpts.symbol),
|
|
98
111
|
toByteArray(allOpts.tokenUri),
|
|
112
|
+
allOpts.votes,
|
|
99
113
|
allOpts.pausable,
|
|
100
114
|
allOpts.explicitImplementations,
|
|
101
115
|
);
|
|
@@ -124,6 +138,7 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract {
|
|
|
124
138
|
addMintable(
|
|
125
139
|
c,
|
|
126
140
|
allOpts.enumerable,
|
|
141
|
+
allOpts.votes,
|
|
127
142
|
allOpts.pausable,
|
|
128
143
|
allOpts.sequential,
|
|
129
144
|
allOpts.access,
|
|
@@ -142,6 +157,7 @@ function addBase(
|
|
|
142
157
|
name: string,
|
|
143
158
|
symbol: string,
|
|
144
159
|
tokenUri: string,
|
|
160
|
+
votes: boolean,
|
|
145
161
|
pausable: boolean,
|
|
146
162
|
explicitImplementations: boolean,
|
|
147
163
|
) {
|
|
@@ -154,6 +170,10 @@ function addBase(
|
|
|
154
170
|
// Set token functions
|
|
155
171
|
c.addUseClause('stellar_tokens::non_fungible', 'Base');
|
|
156
172
|
c.addUseClause('stellar_tokens::non_fungible', 'NonFungibleToken');
|
|
173
|
+
if (votes) {
|
|
174
|
+
c.addUseClause('stellar_governance::votes', 'Votes');
|
|
175
|
+
c.addUseClause('stellar_tokens::non_fungible', 'votes::NonFungibleVotes');
|
|
176
|
+
}
|
|
157
177
|
if (explicitImplementations) c.addUseClause('stellar_tokens::non_fungible', 'ContractOverrides');
|
|
158
178
|
c.addUseClause('soroban_sdk', 'contract');
|
|
159
179
|
c.addUseClause('soroban_sdk', 'contractimpl');
|
|
@@ -165,7 +185,7 @@ function addBase(
|
|
|
165
185
|
traitName: 'NonFungibleToken',
|
|
166
186
|
structName: c.name,
|
|
167
187
|
tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
|
|
168
|
-
assocType:
|
|
188
|
+
assocType: `type ContractType = ${votes ? 'NonFungibleVotes' : 'Base'};`,
|
|
169
189
|
};
|
|
170
190
|
|
|
171
191
|
c.addTraitImplBlock(nonFungibleTokenTrait);
|
|
@@ -181,6 +201,15 @@ function addBase(
|
|
|
181
201
|
c.addFunctionTag(baseFunctions.transfer_from, 'when_not_paused', nonFungibleTokenTrait);
|
|
182
202
|
c.addTraitFunction(nonFungibleTokenTrait, baseFunctions.transfer_from);
|
|
183
203
|
}
|
|
204
|
+
|
|
205
|
+
if (votes) {
|
|
206
|
+
c.addTraitImplBlock({
|
|
207
|
+
traitName: 'Votes',
|
|
208
|
+
structName: c.name,
|
|
209
|
+
tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
|
|
210
|
+
section: 'Extensions',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
184
213
|
}
|
|
185
214
|
|
|
186
215
|
function addBurnable(c: ContractBuilder, pausable: boolean, explicitImplementations: boolean) {
|
|
@@ -263,6 +292,7 @@ function addConsecutive(c: ContractBuilder, pausable: boolean, access: Access, e
|
|
|
263
292
|
function addMintable(
|
|
264
293
|
c: ContractBuilder,
|
|
265
294
|
enumerable: boolean,
|
|
295
|
+
votes: boolean,
|
|
266
296
|
pausable: boolean,
|
|
267
297
|
sequential: boolean,
|
|
268
298
|
access: Access,
|
|
@@ -288,9 +318,22 @@ function addMintable(
|
|
|
288
318
|
} else {
|
|
289
319
|
if (sequential) {
|
|
290
320
|
mintFn =
|
|
291
|
-
effectiveAccess === 'ownable'
|
|
321
|
+
effectiveAccess === 'ownable'
|
|
322
|
+
? votes
|
|
323
|
+
? votesMintFunctions.sequential_mint
|
|
324
|
+
: baseFunctions.sequential_mint
|
|
325
|
+
: votes
|
|
326
|
+
? votesMintFunctions.sequential_mint_with_caller
|
|
327
|
+
: baseFunctions.sequential_mint_with_caller;
|
|
292
328
|
} else {
|
|
293
|
-
mintFn =
|
|
329
|
+
mintFn =
|
|
330
|
+
effectiveAccess === 'ownable'
|
|
331
|
+
? votes
|
|
332
|
+
? votesMintFunctions.mint
|
|
333
|
+
: baseFunctions.mint
|
|
334
|
+
: votes
|
|
335
|
+
? votesMintFunctions.mint_with_caller
|
|
336
|
+
: baseFunctions.mint_with_caller;
|
|
294
337
|
}
|
|
295
338
|
}
|
|
296
339
|
|
|
@@ -405,6 +448,33 @@ const baseFunctions = defineFunctions({
|
|
|
405
448
|
},
|
|
406
449
|
});
|
|
407
450
|
|
|
451
|
+
const votesMintFunctions = defineFunctions({
|
|
452
|
+
mint: {
|
|
453
|
+
args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'token_id', type: 'u32' }],
|
|
454
|
+
code: ['NonFungibleVotes::mint(e, &to, token_id);'],
|
|
455
|
+
},
|
|
456
|
+
mint_with_caller: {
|
|
457
|
+
name: 'mint',
|
|
458
|
+
args: [
|
|
459
|
+
getSelfArg(),
|
|
460
|
+
{ name: 'to', type: 'Address' },
|
|
461
|
+
{ name: 'token_id', type: 'u32' },
|
|
462
|
+
{ name: 'caller', type: 'Address' },
|
|
463
|
+
],
|
|
464
|
+
code: ['NonFungibleVotes::mint(e, &to, token_id);'],
|
|
465
|
+
},
|
|
466
|
+
sequential_mint: {
|
|
467
|
+
name: 'mint',
|
|
468
|
+
args: [getSelfArg(), { name: 'to', type: 'Address' }],
|
|
469
|
+
code: ['NonFungibleVotes::sequential_mint(e, &to);'],
|
|
470
|
+
},
|
|
471
|
+
sequential_mint_with_caller: {
|
|
472
|
+
name: 'mint',
|
|
473
|
+
args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'caller', type: 'Address' }],
|
|
474
|
+
code: ['NonFungibleVotes::sequential_mint(e, &to);'],
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
|
|
408
478
|
const nonFungibleTokenTraitFunctions = pickKeys(baseFunctions, [
|
|
409
479
|
'balance',
|
|
410
480
|
'owner_of',
|
package/src/print.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { Lines } from './utils/format-lines';
|
|
|
4
4
|
import { formatLines, spaceBetween } from './utils/format-lines';
|
|
5
5
|
import { getSelfArg } from './common-options';
|
|
6
6
|
import { compatibleContractsSemver } from './utils/version';
|
|
7
|
-
import { toByteArray } from './utils/convert-strings';
|
|
8
7
|
|
|
9
8
|
const DEFAULT_SECTION = '1. with no section';
|
|
10
9
|
const STANDALONE_IMPORTS_GROUP = 'Standalone Imports';
|
|
@@ -31,6 +30,7 @@ export function printContract(contract: Contract): string {
|
|
|
31
30
|
printUseClauses(contract),
|
|
32
31
|
printMetadata(contract),
|
|
33
32
|
printVariables(contract),
|
|
33
|
+
printTopLevelDeclarations(contract),
|
|
34
34
|
printContractStruct(contract),
|
|
35
35
|
printContractErrors(contract),
|
|
36
36
|
printContractFunctions(contract),
|
|
@@ -364,6 +364,11 @@ function printArgument(arg: Argument): string {
|
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
function printTopLevelDeclarations(contract: Contract): Lines[] {
|
|
368
|
+
if (contract.topLevelDeclarations.length === 0) return [];
|
|
369
|
+
return spaceBetween(...contract.topLevelDeclarations);
|
|
370
|
+
}
|
|
371
|
+
|
|
367
372
|
function printDocumentations(documentations: string[]): string[] {
|
|
368
373
|
return documentations.map(documentation => `//! ${documentation}`);
|
|
369
374
|
}
|
package/src/stablecoin.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { printContract } from './print';
|
|
12
12
|
import { requireAccessControl, type Access } from './set-access-control';
|
|
13
13
|
import { defineFunctions } from './utils/define-functions';
|
|
14
|
+
import { OptionsError } from './error';
|
|
14
15
|
|
|
15
16
|
export const defaults: Required<StablecoinOptions> = {
|
|
16
17
|
...fungibleDefaults,
|
|
@@ -46,6 +47,13 @@ export function isAccessControlRequired(opts: Partial<StablecoinOptions>): boole
|
|
|
46
47
|
export function buildStablecoin(opts: StablecoinOptions): Contract {
|
|
47
48
|
const allOpts = withDefaults(opts);
|
|
48
49
|
|
|
50
|
+
if (allOpts.votes && allOpts.limitations) {
|
|
51
|
+
throw new OptionsError({
|
|
52
|
+
votes: 'Votes extension cannot be used with stablecoin limitations',
|
|
53
|
+
limitations: 'Stablecoin limitations cannot be used with Votes extension',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
const c = buildFungible(allOpts);
|
|
50
58
|
|
|
51
59
|
if (allOpts.limitations) {
|
package/src/test.ts
CHANGED
|
@@ -17,6 +17,10 @@ test.serial('fungible result generated', async t => {
|
|
|
17
17
|
await testGenerate(t, 'Fungible');
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
test.serial('governor result generated', async t => {
|
|
21
|
+
await testGenerate(t, 'Governor');
|
|
22
|
+
});
|
|
23
|
+
|
|
20
24
|
test.serial('non-fungible result generated', async t => {
|
|
21
25
|
await testGenerate(t, 'NonFungible');
|
|
22
26
|
});
|